目前官網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 <fcntl.h>
2 #include <errno.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <ctype.h>
7 #include <sys/wait.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10 #include <sys/statvfs.h>
11 #include <dirent.h>
12 #include <arpa/inet.h>
13 #include <signal.h>
14 #include <glob.h>
15 #include <libubox/blobmsg_json.h>
16 #include <libubox/avl-cmp.h>
17 #include <libubus.h>
18
19 #include <rpcd/plugin.h>
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 <jow@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 <jow@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
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>luci2</title>
<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
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>luci2</title>
<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);
});
},
});
界面效果如下:
登錄界面index.html
鏈接中的id就是每次登錄web後,rpcd返回的隨機值,多html時,我是使用鏈接傳遞這個值得,只要new的luci2中全局變量L.globals.sid 獲取到這個值後,就有權限在前端調用ubus接口方法了。