目前官网openwrt系统集成的web界面,使用luci和luci2,关于luci和luci2的不同,可参见下面链接,作者介绍的很详细:
http://blog.csdn.net/wdsfup/article/details/51024150?locationNum=11&fps=1
本人也是使用luci2实现前后端数据交互的,只是界面使用的是html写成的界面,这样界面制作会更灵活,界面风格能更自由;
luci2的实现原理如下图所示:ubus使用cs模型进行通讯,可以参考下面的作者写的:
http://blog.csdn.net/flexman09/article/details/51722582?locationNum=10&fps=1
1.后台包源码
(1).编写C源码后,编译生成.so放在目录/usr/lib/rpcd/.so(加粗斜线表示在固件中的路径,下同)*中,rpcd会将方法注册到ubus,使用ubus list,查看接口是否注册到ubus; (可以参考luci2.so中各方法的实现)
(2).对ubus的接口,需要在/usr/share/rpcd/acl.d/.json*赋予接口被调用的读写权限;(可以参考luci2.json文件)
(3).可以在后台使用ubus call调用注册的方法,检测使用能使用,排除后台方法调用的错误;
下面是自己编写c代码和编译的CMakeLists.txt后,生成.so文件的过程,还有就是,将.so放到固件中(包的路径package/luci2/;文件路径/packge/luci2/src/rpcd/下面的文件均在此路径下)
C代码文件:lepton.c如下
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #include
16 #include
17 #include
18
19 #include
20
21 static const struct rpc_daemon_ops *ops;
22 static struct blob_buf buf;
23
24 enum {
25 RPC_P_D_DATA,
26 RPC_P_D_COUNT,
27 __RPC_P_D_MAX
28 };
29
30 static const struct blobmsg_policy rpc_ping_data_policy[__RPC_P_D_MAX] = {
31 [RPC_P_D_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
32 [RPC_P_D_COUNT] = { .name = "count", .type = BLOBMSG_TYPE_STRING },
33 };
34
35 static int
36 rpc_luci2_network_ping(struct ubus_context *ctx, struct ubus_object *obj,
37 struct ubus_request_data *req, const char *method,
38 struct blob_attr *msg)
39 {
40 char *arg;
41 char *count = "5";
42
43 struct blob_attr *tb_ping[__RPC_P_D_MAX];
44 blobmsg_parse(rpc_ping_data_policy, __RPC_P_D_MAX, tb_ping,
45 blob_data(msg), blob_len(msg));
46
47 if (!tb_ping[RPC_P_D_DATA] && !tb_ping[RPC_P_D_COUNT])
48 return UBUS_STATUS_INVALID_ARGUMENT;
49
50 arg = blobmsg_get_string(tb_ping[RPC_P_D_DATA]);
51 count = blobmsg_get_string(tb_ping[RPC_P_D_COUNT]);
52
53 const char *cmds[7] = { "ping", "-c", count, "-W", "1", arg, NULL };
54
55 return ops->exec(cmds, NULL, NULL, NULL, NULL, NULL, ctx, req);
56 }
57
58 static int
59 rpc_luci2_print_lepton(struct ubus_context *ctx, struct ubus_object *obj,
60 struct ubus_request_data *req, const char *method,
61 struct blob_attr *msg)
62 {
63 char conf[10] = "lepton";
64 blob_buf_init(&buf, 0);
65 blobmsg_add_string(&buf, "name", conf);
66
67 ubus_send_reply(ctx, req, buf.head);
68 return 0;
69 }
70
71 static int
72 rpc_luci2_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx)
73 {
74 int rv = 0;
75
76 static const struct ubus_method luci2_network_methods[] = {
77 UBUS_METHOD_NOARG("lepton", rpc_luci2_print_lepton), //无参操作,ubus接口lepton.network对应的方法名
78 UBUS_METHOD("ping1", rpc_luci2_network_ping, //有参操作,ubus接口lepton.network对应的方法名
79 rpc_ping_data_policy)
80 };
81
82 static struct ubus_object_type luci2_network_type =
83 UBUS_OBJECT_TYPE("luci-rpc-luci2-lepton", luci2_network_methods);
84
85 static struct ubus_object network_obj = {
86 .name = "lepton.network", //接口名
87 .type = &luci2_network_type,
88 .methods = luci2_network_methods,
89 .n_methods = ARRAY_SIZE(luci2_network_methods),
90 };
91
92 ops = o;
93
94 rv |= ubus_add_object(ctx, &network_obj);
95
96 return rv;
97 }
98
99 struct rpc_plugin rpc_plugin = {
100 .init = rpc_luci2_api_init
101 };
对应的CMakeLists.txt,此处还有别的C文件也在此编译,看lepton的部分即可:
1 cmake_minimum_required(VERSION 2.6)
2
3
4 PROJECT(luci2-plugin C)
5
6 ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations -Iinclude)
7
8 SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
9
10 IF(APPLE)
11 INCLUDE_DIRECTORIES(/opt/local/include)
12 LINK_DIRECTORIES(/opt/local/lib)
13 ENDIF()
14
15 FIND_LIBRARY(crypt NAMES crypt)
16 IF(crypt STREQUAL "LIBS-NOTFOUND")
17 SET(crypt "")
18 ENDIF()
19
20 ADD_LIBRARY(luci2-plugin MODULE luci2.c)
21 TARGET_LINK_LIBRARIES(luci2-plugin ubox ubus ${crypt})
22 SET_TARGET_PROPERTIES(luci2-plugin PROPERTIES OUTPUT_NAME luci2 PREFIX "")
23
24 ADD_LIBRARY(bwmon-plugin MODULE bwmon.c)
25 SET_TARGET_PROPERTIES(bwmon-plugin PROPERTIES OUTPUT_NAME bwmon PREFIX "")
26
27 ADD_LIBRARY(firewall-plugin MODULE firewall.c)
28 SET_TARGET_PROPERTIES(firewall-plugin PROPERTIES OUTPUT_NAME firewall PREFIX "")
29
30 ADD_LIBRARY(wireless-plugin MODULE wireless.c)
31 SET_TARGET_PROPERTIES(wireless-plugin PROPERTIES OUTPUT_NAME wireless PREFIX "")
32
33 ADD_LIBRARY(qos-plugin MODULE qos.c)
34 SET_TARGET_PROPERTIES(qos-plugin PROPERTIES OUTPUT_NAME qos PREFIX "")
35
36 ADD_LIBRARY(lepton-plugin MODULE lepton.c)#此处编译本人写的C源码
37 SET_TARGET_PROPERTIES(lepton-plugin PROPERTIES OUTPUT_NAME lepton PREFIX "")
38
39 SET( MY_DIR sub_source/my_string)
40 SET(SRC_LIST wuxian.c;${MY_DIR}/my_string.c;${MY_DIR}/mylist.c)
41 ADD_LIBRARY(wuxian-plugin MODULE ${SRC_LIST})
42
43 include_directories(sub_source/my_string)
44 SET_TARGET_PROPERTIES(wuxian-plugin PROPERTIES OUTPUT_NAME wuxian PREFIX "")
45
46 INSTALL(TARGETS luci2-plugin bwmon-plugin firewall-plugin wireless-plugin qos-plugin wuxian-plugin lepton-plugin LIBRARY DESTINATION lib)
以上两个文件写完后,编译后在build_dir/target_XXX/luci2_xXX/rpcd路径下,可以看到编译生成的.so文件,但此时,该.so还没有编译到固件中,需要在引导Makefile中,写明它的安装路径
Makefile文件,引导.so编译到固件,如下注释部分
1 #
2 # Copyright (C) 2013 Jo-Philipp Wich @openwrt .org>
3 #
4 # Licensed under the Apache License, Version 2.0.
5 #
6
7 include $(TOPDIR)/rules.mk
8
9 PKG_NAME:=luci2
10 #PKG_VERSION:=$(shell git --git-dir=$(CURDIR)/../.git log -1 --pretty="%ci %h" | awk '{ print $$1 "-" $$4 }')
11 PKG_VERSION:=2015-02-14-e452ca6
12 PKG_MAINTAINER:=Jo-Philipp Wich @openwrt.org>
13
14 PKG_LICENSE:=Apache-2.0
15 PKG_LICENSE_FILES:=
16
17 PKG_BUILD_PARALLEL:=1
18
19 include $(INCLUDE_DIR)/package.mk
20 include $(INCLUDE_DIR)/cmake.mk
21
22 define Build/Prepare
23 $(INSTALL_DIR) $(PKG_BUILD_DIR)
24 $(CP) ./src/* $(PKG_BUILD_DIR)/
25 endef
26
27 define Package/luci2
28 SECTION:=luci2
29 CATEGORY:=LuCI2
30 TITLE:=LuCI2 UI
31 DEPENDS:=+rpcd +rpcd-mod-iwinfo +uhttpd +uhttpd-mod-ubus +libubox +libubus
32 endef
33
34 define Package/luci2/description
35 Provides the LuCI2 web interface with standard functionality.
36 endef
37
38 define Package/luci2/install
39 $(INSTALL_DIR) $(1)/www
40 $(CP) ./htdocs/* $(1)/www/
41 $(INSTALL_DIR) $(1)/usr/share/rpcd
42 $(CP) ./share/* $(1)/usr/share/rpcd/
43 $(INSTALL_DIR) $(1)/usr/lib/rpcd
44 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/luci2.so $(1)/usr/lib/rpcd/
45 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/bwmon.so $(1)/usr/lib/rpcd/
46 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/firewall.so $(1)/usr/lib/rpcd/
47 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/wireless.so $(1)/usr/lib/rpcd/
48 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/qos.so $(1)/usr/lib/rpcd/
49 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/wuxian.so $(1)/usr/lib/rpcd/
50 $(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/lepton.so $(1)/usr/lib/rpcd/ #安装在固件中的路径是/usr/lib/rpcd
51 $(INSTALL_DIR) $(1)/usr/libexec $(1)/www/cgi-bin
52 $(INSTALL_BIN) $(PKG_BUILD_DIR)/io/luci2-io $(1)/usr/libexec/
53 $(LN) /usr/libexec/luci2-io $(1)/www/cgi-bin/luci-upload
54 $(LN) /usr/libexec/luci2-io $(1)/www/cgi-bin/luci-backup
55 $(INSTALL_DIR) $(1)/etc
56 $(CP) ./script/etc/* $(1)/etc/
57 $(INSTALL_DIR) $(1)/usr/bin
58 $(CP) ./script/usr/bin/* $(1)/usr/bin/
59 $(INSTALL_DIR) $(1)/etc/config
60 $(CP) ./uci_file/* $(1)/etc/config/
61 endef
62
63 define Package/luci2/postinst
64 #!/bin/sh
65
66 if [ "$$(uci -q get uhttpd.main.ubus_prefix)" != "/ubus" ]; then
67 uci set uhttpd.main.ubus_prefix="/ubus"
68 uci commit uhttpd
69 fi
70
71 exit 0
72 endef
73
74 $(eval $(call BuildPackage,luci2))
经过以上的部分,编译后,能在固件中调用到注册的ubus接口,但不能通过web界面调用到注册的接口,因为web界面调用注册的ubus接口,还需要权限,下面在(package/luci2/share/acl.d)路径下,添加lepton.json文件,使注册的接口或得访问权限:
1 {
2 "test":{
3 "description": "for learn luci2",
4 "read":{
5 "ubus":{
6 "lepton.network":[
7 "ping1",
8 "lepton"
9 ]
10 }
11 }
12 },
13 }
2.使用putty,进入路由系统验证注册的方法
经下面验证,方法均可成功调用:
3.前端对ubus接口调用
在官网集成的luci2源码中有对接口的封装,源码路径(package/luci2/htdocs/),此路径下放着web前端界面的文件,生成固件后在目录*www下,查看/etc/config/uhttpd可以知道web服务器默认访问的路径就是www,我们的html文件就放在www下,下面是前端的源码实例解析(一个登录界面index.html,一个测试界面test.html)
原luci2的www中文件替换如下:(编译前替换到package/luci2/htdocs/下,或使用工具WinSCP替换)
下面是luci2文件夹下的文件:
文件说明:bootstrap.js, jquery.peity.js, jquery-1.9.1.js保留这三个文件是因为我的html要加载js代码,如果开发时,有js代码库加载,这三个文件可以不要; rpc.js封装了ubus接口发送的函数,不可少; session.js文件中是界面心跳机制的实现,不可少; luci2.js中封装了很多方法,不可少;
注:此处没有加载uci.js,项目中最好保留,因为它对ubus调用uci方法的封装做的很好。
添加index.js和test.js后,需要在luci2.js文件中加载,加载代码如下面截图
下面是前端新增的5个文件的代码:
1.index.html
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>luci2title>
<script type="text/javascript" src="/luci2/jquery-1.9.1.js">script>
<script type="text/javascript" src="/luci2/jquery.peity.js">script>
<script type="text/javascript" src="/luci2/bootstrap.js">script>
<script type="text/javascript" src="luci2/luci2.js">script>
<script type="text/javascript" src="luci.js">script>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
head>
<body>
<p>
login p>
<p>
username:<input id="username" type="text" value="root"/>p>
<p>
password:<input id="password" type="text" />p>
<p>
<input id="login" type="button" value="login" />p>
body>
html>
2.test.html
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>luci2title>
<script type="text/javascript" src="/luci2/jquery-1.9.1.js">script>
<script type="text/javascript" src="/luci2/jquery.peity.js">script>
<script type="text/javascript" src="/luci2/bootstrap.js">script>
<script type="text/javascript" src="luci2/luci2.js">script>
<script type="text/javascript" src="luci.js">script>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
head>
<body>
<p>
name:
<input id="name" type="text" />p>
<p>
p>
<p>
p>
<p>
ping:p>
<p>
addr:
<input id="ping_addr" type="text" />p>
<p>
count: <input id="ping_count" type="text" />p>
<p>
<textarea id="ping_text">result:textarea>p>
<p>
<input id="ping_btn" type="button" value="start" />p>
body>
html>
3.luci.js
$(function () {
var hr = window.location.href;
var g_sid = hr.substring(hr.lastIndexOf(':') + 1, hr.length);
var ln_name = hr.substring(hr.lastIndexOf('/') + 1, hr.lastIndexOf('.'));//使用链接传递权限id值
var L;
if (g_sid && g_sid.match(/^[a-f0-9]{32}$/)) {
if (ln_name == "test") {
L = new LuCI2("test");
L.globals.sid = g_sid; //有这个值后,才能获取到调用后台ubus接口的权限
L.setHash('id', L.globals.sid);
L.session.startHeartbeat();
L.test.load();
} else {
var hr = ('%s/test.html').format(window.location.origin);
window.location.href = hr;
}
} else {
L = new LuCI2("index");
}
$("#ping_btn").click(function () {
L.test.ping_opt();
});
$("#login").click(function () {
L.index.load();
});
});
4.luci2/index.js
Class.extend({
load: function () {
var u = document.getElementById("username").value;
var p = document.getElementById("password").value;
L.globals.sid = '00000000000000000000000000000000';
L.session.login(u, p).then(function (response) {
L.globals.sid = response.ubus_rpc_session; //此处获取到后台rpcd发送的访问ubus接口的权限了
L.setHash('id', L.globals.sid);
L.session.startHeartbeat();
var hr = ('%s/test.html#id:%s').format(window.location.origin, L.globals.sid);
window.location.href = hr;
});
},
});
5.luci2/test.js
Class.extend({
get_name: L.rpc.declare({
object: 'lepton.network',
method: 'lepton',
expect: { '': {} }
}), //后台ubus接口在前端调用的格式函数,这个是无参,有返回值
runPing: L.rpc.declare({
object: 'lepton.network',
method: 'ping1',
params: ['data', 'count'],
expect: { '': { code: -1 } }
}), //有参,有返回值
load: function () {
L.test.get_name().then(function (info) { //接口函数的调用
document.getElementById("name").value = info.name; //通过ID使静态界面,获取到固件动态值
});
},
ping_opt: function () {
var ip_ping = document.getElementById("ping_addr").value;
var ping_count = document.getElementById("ping_count").value;
if (ping_count == "") {
ping_count = "1";
}
var ping_v = parseInt(ping_count);
if (ping_v > 0 && ping_v <= 10) {
ping_count = ("%s").format(ping_v);
} else {
ping_count = "1";
}
$("#ping_text").text(("ping : %s ...").format(ip_ping));
L.test.runPing(ip_ping, ping_count).then(function (rv) {
$("#ping_text").text(rv.stdout);
});
},
});
链接中的id就是每次登录web后,rpcd返回的随机值,多html时,我是使用链接传递这个值得,只要new的luci2中全局变量L.globals.sid 获取到这个值后,就有权限在前端调用ubus接口方法了。