用JSONRPC改造智能家居源码——编写一个led测试程序

一、背景

  前阶段复刻了B站up主晨少开源的智能家居项目,基于正点原子imx6ull阿尔法开发板实现。但其QT端的源码会有些冗杂,既有界面设计的源码,又有硬件操作的代码。看了小红书某个哥们的优快云文章,知道了韦东山的嵌入式Linux快速入门实验班提出了一个JSONRPC的思想,实现了前后台程序分离。因此想将其实现到我的项目代码里。

二、JSONRPC简介

  JSONRPC分JSON和RPC组成。JSON实际上是指JSON格式,它的英文是JavaScript Object Notation, JavaScript 对象表示法。这是一种很常见的数据格式,比如在使用MQTT向某个云平台传输数据的时候就会用到。
  它的关键成员是“name:value”, value 有多种形式。就是类似于一下这种键值对的形式,它的值还可以是数组、另一个JSON对象。(这里只是简述,不作过多介绍,具体语法可以自己去了解。这边引用了韦东山的嵌入式Linux应用开发实验班(快速入门)pdf的内容,韦东山老师的资料总是干练简洁直击重点,感兴趣可以下载来看看)

{ "name": "John", "age": 30, "isStudent": false , "ptr": null }

  RPC的英文是Remote Procedure Call,也就是远程调用(通过英文全称,来学习一个专业术语挺好)。远程调用就是字面意思,正常调用是定义一个xxx.h文件,然后我include进行,就可以调用函数。远程调用相反,就是在远边调用,比如在服务器进程中调用。JSONRPC是基于网络进行通信的。相当于我可以创建一个服务器进程,将硬件相关的代码放在这个进程里,将QT程序作为客户端进程,客户端进程只包含界面相关的代码,当我需要控制硬件,就通过RPC向服务器进程发信号。这样子就实现了前后端分离的思想,使得程序拥有更加清晰的架构。
  本篇文章的目的,就是编写出来一个led测试程序:创建一个服务器进程,这个进程存放led硬件相关代码,如open、write,监听客户端的请求,再将QT程序改造成一个客户端进程,通过按键向服务器发送请求,实现RPC。

三、实现历程

3.1 在ubuntu虚拟机上编译运行测试JSONRPC库

  在这边使用的是正点原子的虚拟机,韦东山的嵌入式Linux应用开发实验班(快速入门)pdf。
  韦东山把几个需要的文件放置在以下目录当中,要下载这些文件可以观看韦东山的嵌入式Linux应用开发实验班下载。

F:\01_all_series_quickstart\04a_嵌入式Linux应用开发实验班(快速入门)\05_使用JsonRPC实现前后台\source\5-4_JSON-RPC示例与情景分析\5-4-1_JSON-RPC上机实验

在这里插入图片描述
  简述一下这几个文件,第一个是测试程序,第二个就是JSONRPC的库,第三个是libev,JSONRPC依赖于这个库。接下来就是韦东山的PDF里面的内容(在pdf的5.5.7 上机实验里,pdf写的很详细),先把这三个文件放在同个文件夹。

1、编译libev库

$ tar xjf libev_pc.tar.bz2
$ cd libev_pc/
$ CC=gcc ./configure --prefix=$PWD/tmp
$ make -j 16
$ make install
$ ls tmp/
include lib share
$ sudo cp -rf tmp/include/* /usr/include/
$ sudo cp -rfd tmp/lib/* /usr/lib

2、编译JSONRPC库

$ sudo apt install libtool
$ tar xjf jsonrpc-c_pc.tar.bz2
$ cd jsonrpc-c_pc/
$ autoreconf -i
$ CC=gcc ./configure --prefix=$PWD/tmp
$ make -j 16
$ make install
$ ls tmp/
include lib
$ sudo cp -rf tmp/include/* /usr/include/
$ sudo cp -rfd tmp/lib/* /usr/lib

3、编译测试程序

$ tar xjf json-rpc_test.tar.bz2
$ cd json-rpc_test/
$ make

4、执行程序

$ ./rpc server & #开启rpc服务器
[1] 15028
$ ./rpc add 2 6 #远程调用服务器里的add函数
sum = 8
碰到的问题

  在运行测试程序时,碰到了问题
/home/alientek/Desktop/json-rpc/libev_pc/ev.c:3363: undefined reference to `floor’
  floor函数是数学库math.h中的函数,在LInux系统中,使用GCC编译时,需要显式链接数学库libm,否则会出现undefined reference to floor的错误。在Makefile中添加-lm指令即可
在这里插入图片描述
测试成功
在这里插入图片描述

3.2 在Arm板上运行JSONRPC

交叉编译rpc库

韦东山的教程如下
1、 编译 libev 库

$ tar xjf libev.tar.bz2
$ cd libev/
$ ./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
$ make -j 16
$ make install
$ ls tmp/
include lib share

2、编译jsonrpc库

$ sudo apt install libtool
$ tar xjf jsonrpc-c.tar.bz2
$ cd jsonrpc-c/
$ autoreconf -i
$ ./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
CFLAGS="-I$PWD/../libev/tmp/include" LDFLAGS="-L$P
WD/../libev/tmp/lib"
$ make -j 16
$ make install
$ ls tmp/
include lib

3、编译测试程序

$ cd rpc_server
$ make
$ adb push rpc_server /root
$ cd ../rpc_client
$ make
$ adb push rpc_client /root

4、执行程序

$ ./rpc_server &
$ ./rpc_client led 0
$ ./rpc_client led 1
$ ./rpc_client dht11

  基本跟在ubuntu上编译没啥区别,就是把编译器换成交叉编译工具链。但我在这边碰到的问题层出不穷(因为太疏忽了)

碰到的问题

问题1 make: arm-buildroot-linux-gnueabihf-gcc: Command not found

  我首先是查看了Makefile,发现他是这样定义的。
在这里插入图片描述
  我查看了一下之前编译驱动的Makefile,如以下所示,所以我就把上面那个改成CROSS_COMPILE的形式(此时我并没有注意到问题的关键是那个buildroot,我自己的交叉编译工具链是没有buildroot的,韦东山的是有这个)。
在这里插入图片描述
  后面确实没有再报错,我就以为我解决了,接下来就出现了问题。

问题2 运行程序,一堆莫名其妙报错,错误如下

# ./rpc_server
./rpc_server: line 2: : not found
./rpc_server: line 3: : not found
./rpc_server: line 4: syntax error: unexpected "("

  很奇怪,我运行的应用程序,怎么会有语法错误。问ai,ai的答复是
错误信息 ./rpc_server: line 2: : not foundsyntax error: unexpected "(" 等,通常表明系统试图将文件当作脚本执行,而不是可执行文件。这可能是因为文件的格式或内容不符合当前系统的预期。这种情况下,文件可能是一个为其他架构编译的可执行文件,或者文件的格式被错误地处理了。学到了,还得是AI。我用file命令检查了rpc_server。发现这个文件是x86-64架构的

alientek@ubuntu16:~/Desktop/json-rpc_control_hardware/rpc_server$ file rpc_server
rpc_server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=455967b3c8e920170fd396b11fbcce2fac0bb090, not stripped

  同时我也疑惑,我用的明明是交叉编译工具链,怎么会整出来个x86呢?于是echo $(CROSS_COMPILE),发现为空, 我很疑惑,那我以前是如何成功编译驱动的?ai说可能是我之前编译驱动时,内核模块的构建使用了内核的构建系统。

make -C $(KERN_DIR) M=$(PWD) modules

  这部分构建可能会设置或覆盖一些环境变量,影响后续用户空间程序的构建(存疑)。
  后续我重新编译了一下,详细看了输出信息,发现了问题,输出信息指示没有找到arm-buildroot-linux-gnueabihf-gcc,这边有个buildroot,正点原子的是没有的。因为没有找到交叉编译工具链,所以才退化成gcc,才编译出来的x86架构。

alientek@ubuntu16:~/Desktop/json-rpc_control_hardware/jsonrpc-c$ ./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp CFLAGS="-I$PWD/../libev/tmp/include" LDFLAGS="-L$PWD/../libev/tmp/lib"
checking for arm-buildroot-linux-gnueabihf-gcc... no
checking for gcc... gcc
...........................................

make[2]: Entering directory '/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/example'
gcc -DHAVE_CONFIG_H -I. -I..  -lm -I../include   -I/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/../libev/tmp/include -MT server-server.o -MD -MP -MF .deps/server-server.Tpo -c -o server-server.o `test -f 'server.c' || echo './'`server.c
mv -f .deps/server-server.Tpo .deps/server-server.Po
/bin/bash ../libtool  --tag=CC   --mode=link gcc  -I/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/../libev/tmp/include -lev ../src/libjsonrpcc.la -L/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/../libev/tmp/lib -o server server-server.o
libtool: link: gcc -I/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/../libev/tmp/include -o .libs/server server-server.o  /home/alientek/Desktop/json-rpc_control_hardware/libev/tmp/lib/libev.so ../src/.libs/libjsonrpcc.so -L/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/../libev/tmp/lib -Wl,-rpath -Wl,/home/alientek/Desktop/json-rpc_control_hardware/libev/tmp/lib -Wl,-rpath -Wl,/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/tmp/lib
make[2]: Leaving directory '/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c/example'
make[2]: Entering directory '/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c'
make[2]: Leaving directory '/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c'
make[1]: Leaving directory '/home/alientek/Desktop/json-rpc_control_hardware/jsonrpc-c'

  所以,折腾这么久,发现只是我疏忽了一个buildroot,血亏,浪费了很多时间,下次还是要仔细一点。解决了这个问题,就没啥大问题了,程序也能在板子运行。从中我也感受到了,其实我并不需要在板子上下载这些库,只要在虚拟机上搞好,链接成应用程序就可以了,之前我还以为也需要在板子上下载jsonrpc的库。

3.3 改造QT程序

  浅析一下改造好的QT程序。分两个进程,一个是rpcserver进程,一个是QT进程。

rpcserver进程

  在该进程的主函数里,运行两个初始化函数,一个是led的初始化,其实只是打开节点,还有一个服务器的初始化函数。

int main(int argc, char **argv)
{
    led_init();
    //dht11_init();
    RPC_Server_Init();   
    return 0;
}

  服务器的初始化代码如下所示,创建jrpc_server结构体,然后初始化这个服务器,然后往里注册一个函数,server_led_control。

static struct jrpc_server my_server;
int RPC_Server_Init(void) 
{
    int err;
    
    err = jrpc_server_init(&my_server, PORT);
    if (err)
    {
        printf("jrpc_server_init err : %d\n", err);
    }
    printf("jrpc_server_init success \r\n");
	
    jrpc_register_procedure(&my_server, server_led_control, "led_control", NULL );
    //jrpc_register_procedure(&my_server, server_dht11_read, "dht11_read", NULL );

    printf("jrpc_register_procedure run \r\n");
    jrpc_server_run(&my_server);
    jrpc_server_destroy(&my_server);

    return 0;
}

  server_led_control函数如下,可以看到,里面的核心是获取rpc客户端传送过来的参数,调用led_control控制灯亮灯灭。

cJSON * server_led_control(jrpc_context * ctx, cJSON * params, cJSON *id) {
    cJSON * status = cJSON_GetArrayItem(params,0);
    led_control(status->valueint);	
    return cJSON_CreateNumber(0);
}

rpcclient进程(QT端代码)

  在客户端,同样,首先会初始化客户端的代码,


int main(int argc, char *argv[])
{
    //RPC_Client_Init();
    QApplication a(argc, argv);
    RPC_Client_Init();
    MainWindow w;
    w.show();
    return a.exec();
}

  客户端初始化代码如下,创建一个客户端的socket对象,最后再去connect服务器。可以看出,connect的第一个参数是客户端的文件描述符数值,第二个参数则是服务器的地址。这样客户端和服务器就联系起来了。

int RPC_Client_Init(void) 
{
    int iSocketClient;
    struct sockaddr_in tSocketServerAddr;
    int iRet;

    iSocketClient = socket(AF_INET, SOCK_STREAM, 0);

    tSocketServerAddr.sin_family      = AF_INET;
    tSocketServerAddr.sin_port        = htons(PORT);  /* host to net, short */
    //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    inet_aton("127.0.0.1", &tSocketServerAddr.sin_addr);
    memset(tSocketServerAddr.sin_zero, 0, 8);


    iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
    if (-1 == iRet)
    {
        printf("connect error!\n");
        return -1;
    }

    g_iSocketClient = iSocketClient;
    printf("g_iSocketClient = %d \n", g_iSocketClient);
    return iSocketClient;    
}

  QT端通过按钮向服务器发送rpc请求。

/* control led */
void MainWindow::on_pushButton_clicked()
{
  static int status = 1;

  /* 2. control LED */
  rpc_led_control(status);

  status = !status;
}

  rpc_led_control代码如下,sprintf那里构造了一个json格式的buf,然后发送给服务器,从中可以看到客户端构造的json格式里,method也为led_control(同服务器那里注册的参数一致),服务器会解析这个json格式数据,如果发现method的值和服务器那边注册的参数一致,就会调用led_control,并一段json格式的数据。
  在这里,如果成功发送,send函数会返回一个和buf一样长的数据,在while循环里,会反复读看看服务器有没有发来数据,当数据为1,并且为回车换行符时,数据无效,继续读。当读到一个有效数据,cJSON_Parse是创建一个JSON数据格式,cJSON_GetObjectItem是从中获取result的值。

int rpc_led_control(int on)
{
    char buf[100];
    int iLen;
    int ret = -1;

    int iSocketClient = g_iSocketClient;

    sprintf(buf, "{\"method\": \"led_control\", \"params\": [%d], \"id\": \"2\" }", on);
    iLen = send(iSocketClient, buf, strlen(buf), 0);
    if (iLen ==  strlen(buf))
    {
        while (1) 
        {
            iLen = read(iSocketClient, buf, sizeof(buf));
            buf[iLen] = 0;
            if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))
                continue;
            else
                break;
        } 
        
        if (iLen > 0)
        {
            cJSON *root = cJSON_Parse(buf);
            cJSON *result = cJSON_GetObjectItem(root, "result");
            ret = result->valueint;
            cJSON_Delete(root);
            return ret;
        }
        else
        {
            printf("read rpc reply err : %d\n", iLen);
            return -1;
        }
    }
    else
    {
        printf("send rpc request err : %d, %s\n", iLen, strerror(errno));
        return -1;
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值