最近在学习OpenWrt,需要在OpenWrt的WEB界面增加内容,本文将讲述修改OpenWrt的过程和其中遇到的问题。
一、WEB界面开发
LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三层架构,使用Lua脚本开发,所以开发LuCI的配置界面不需要编辑任何的Html代码,除非想自己单独去创建网页(View层),否则我们基本上只需要修改Model层就可以了。
首先我们讲述如何在web界面增加一个新的选项,如下图“System”旁边的“SZ-Loogson”选项卡。
在文件系统目录“/usr/lib/lua/luci/controller/admin”下创建loogson.lua文件,文件内容如下:
点击(此处)折叠或打开
module("luci.controller.admin.loogson", package.seeall)
function index()
entry({"admin", "loogson"}, alias("admin", "loogson", "loogson"), _("SZ-Loogson"), 30).index = true
entry({"admin", "loogson", "loogson"}, cbi("admin_loogson/loogson"), _("BoardType"), 1)
entry({"admin", "loogson", "control"}, cbi("admin_loogson/control"), _("Control"), 2)
end
说明:
1) lua单行注释使用“--”,类似于C语言的“//”,多行注释时,“--[[”类似C语言中的“/*”,“]]--”类似C语言中的“*/”。
2) 第1行定义了模块的入口。即“/usr/lib/lua/luci/controller/admin”所示的目录下建立一个loogson.lua文件。如果程序比较多,可能分为好几个模块,那么可以在controller下再建立一个子目录,比如controller/loogsonapp/,那么就可以写成“luci.controller.loogsonapp.loogson”。
3) 从第3行到10行即是function index函数,该函数定义了SZ-Loogson下各个选项卡。
entry表示添加一个新的模块入口,entry的定义如下,其中后两项都是可以为空:
点击(此处)折叠或打开
entry(path, target, title=nil, order=nil)
“path”是访问的路径,路径是按字符串数组给定的,比如路径按如下方式写“{"admin", "loogson", "control"}”,那么就可以在浏览器里访问“http://192.168.1.1/cgi-bin/luci/admin/loogson/control”来访问这个脚本。其中的“admin”表示为管理员添加脚本,“loogson”即为一级菜单名,“control”为菜单项名。系统会自动在对应的菜单中生成菜单项。比如想在“System”菜单下创建一个菜单项,那么一级菜单名可以写为“system”。
“target”为调用目标,调用目标分为三种,分别是执行指定方法(Action)、访问指定页面(Views)以及调用CBI Module。
第一种可以直接调用指定的函数,比如点击菜单项就直接重启路由器等等,比如写为“call("function_name")”,然后在该lua文件下编写名为function_name的函数就可以调用了。
第二种可以访问指定的页面,比如写为“template("myapp/mymodule")”就可以调用/usr/lib/lua/luci/view/myapp/mymodule.htm文件了。
第三种主要应用在配置界面,比如写为“cbi("myapp/mymodule")”就可以调用/usr/lib/lua/luci/model/cbi/myapp/mymodule.lua文件了。
title和order是针对管理员菜单的,其中的title即是显示在网页上的内容。这里我们创建“/usr/lib/lua/luci/controller/loogson.lua”文件,定义我们的入口为“loogson”。
4) 从上面的程序可以看出,在“SZ-Loogson”选项卡下共有两个分选项,名称分别为“BoardType”和“Control”。根据cbi指示的目录,在“/usr/lib/lua/luci/model/cbi/admin_loogson”目录下有loogson.lua和control.lua两个文件,两个界面类似,我们选取"Control"界面讲述。
当点击上图中的"Control"选项时,即可进入如下界面:
此界面对应的程序在“/usr/lib/lua/luci/model/cbi/admin_loogson/control.lua”下,具体内容为:
点击(此处)折叠或打开
require("luci.sys")
require("luci.sys.zoneinfo")
require("luci.tools.webadmin")
require("luci.fs")
require("luci.config")
local m, s, o
m = Map("loogson", translate("Control"), translate("This is design by Davied Huang, in order to control loogson board, such as led、beep、and adc."))
m:chain("luci")
s = m:section(TypedSection, "controlboard", translate("Control Board"))
s.anonymous = true
s.addremove = false
s:tab("led", translate("Control LED"))
s:tab("beep", translate("Control Beep"))
--s:tab("adc", translate("Control Adc"))
--
-- LED
--
o = s:taboption("led", ListValue, "lednum", translate("LED NUM:"))
o.default = 0
o.datatype = "uinteger"
o:value(0, translate("LED0"))
o:value(1, translate("LED1"))
o:value(2, translate("LED2"))
o = s:taboption("led", ListValue, "ledstatus", translate("LED STATUS:"))
o.default = 1 --off status
o.datatype = "uinteger"
o:value(0, translate("LED ON"))
o:value(1, translate("LED OFF"))
--
-- BEEP
--
o = s:taboption("beep", ListValue, "beepstatus", translate("BEEP STATUS:"))
o.default = 1 --off status
o.datatype = "uinteger"
o:value(0, translate("ON"))
o:value(1, translate("OFF"))
o = s:taboption("beep", Value, "beepfreq", translate("BEEP FREQ:"))
o.datatype = "uinteger"
local apply = luci.http.formvalue("cbi.apply")
if apply then
io.popen("/etc/init.d/loogson restart")
end
return m
说明:
1) 此处我们使用UCI(Unified Configuration Interface,统一配置接口)开发方式,第7行定义了三个局部变量。
2) 第9行引用了一个“Map”调用,该调用的定义为:
点击(此处)折叠或打开
m = Map("配置文件文件名", "配置页面标题", "配置页面说明")
第一个参数为配置文件存储的文件名,不包含路径,比如按上述创建的话,应该写为“loogson”,配置文件的存储地址为:/etc/config。第二与第三个参数是用在来页面上显示的,如图所示。
3) 接下来需要创建与配置文件中对应的Section,Section分为两种,NamedSection和TypedSection,前者根据配置文件中的Section名,而后者根据配置文件中的Section类型,这里我们使用后者。我们设定不显示Section的名称(“s.anonymous = true”),以及不允许增加或删除Section(“s.addremove = false”)。controlboard即为1)中配置文件其中的一个配置。
4) 接下来我们定义了两个选项卡,分别为“Control LED”和“Control Beep”。如上图所示。
5) 对于LED选项卡的程序为24-35行。首先创建LED的Section中不同内容的交互(创建Option),常见的有Value(文本框)、ListValue(下拉框)、Flag(选择框)等。创建Option的过程非常简单,而且创建后系统会无需考虑读取以及写入配置文件的问题,系统都会自动处理。25行定义了Option的默认值,26行定义了它的数据类型,此处为×××。27到29定义了它的三个取值,比如如果你选“LED1”的话,实际写到配置文件中的值为1。
6) 对于使用UCI的方式,首先需要创建对应的配置文件(如果配置文件不存在的话,访问配置页面将会报错),格式即为linux配置文件的格式,文件存储在文件系统“/etc/config”目录下,对于本文即在“/etc/config/loogson”,内容如下:
点击(此处)折叠或打开
config boardinfo
option ipaddr1 '192.168.123.212'
option netmask1 '255.0.0.0'
option boardname '1'
config controlboard
option beepfreq '100'
option beepstatus '0'
option lednum '2'
option ledstatus '0
7) 应用配置以后希望配置立即生效,51行到54行的代码就是判断是否点击了“应用”按钮,如果点击了“应用”按钮就执行默认的脚本程序。
二、OpenWrt添加模块(package)
OpenWrt是一个比较完善的嵌入式Linux开发平台,在无线路由器应用上已有100多个软件包。人们可以在其基础上增加软件包,以扩大其应用范围。
2.1、准备工作
OpenWrt在增加软件方面极其方便,按照OpenWrt的约定就可以很简单完成。加入的软件包可以是网上下载的开源软件或自行开发的软件。加入软件包需要在package目录下创建一个目录。然后创建一个Makefile与OpenWrt建立联系,Makefile需要遵循OpenWrt的约定。另外可以创建一个patchs目录保存patch文件,对下载的源代码进行修改。
由于本文所建立的模块是基于luci的,所以在OpenWrt的“package/feeds/luci”目录下建立“szloogson”目录。
2.2、模块Makefile
在“szloogson”目录下建立一个“Makefile”文件,该文件的如下所示:
点击(此处)折叠或打开
#
# Copyright (C) 2010-2014 Davied Huang Wich <apple_guet@126.com>
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-szloogson
PKG_VERSION=1.0
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-szloogson
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=shenzhou loogson for LuCI
PKGARCH:=all
endef
define Package/luci-app-njitclient/description
This package contains LuCI configuration pages for shenzhou loogson.
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Configure
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CROSS_COMPILE="$(TARGET_CROSS)" \
ARCH="$(ARCH)"
endef
define Package/luci-app-szloogson/install
#install shell
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/loogson.init $(1)/etc/init.d/loogson
#install config
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/loogson.config $(1)/etc/config/loogson
#install execute bin
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/gsc3280_led $(1)/usr/sbin/gsc3280_led
#install luci
mkdir -p $(1)/usr/lib/lua/luci/controller/admin
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
$(INSTALL_DATA) ./src/luci/controller/admin/loogson.lua $(1)/usr/lib/lua/luci/controller/admin/loogson.lua
mkdir -p $(1)/usr/lib/lua/luci/model/cbi/admin_loogson
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi
$(INSTALL_DATA) ./src/luci/model/cbi/admin_loogson/* $(1)/usr/lib/lua/luci/model/cbi/admin_loogson/
endef
$(eval $(call BuildPackage,luci-app-szloogson)
说明:
2.2.1、第8行“$(TOPDIR)/rules.mk”
“$(TOPDIR)/rules.mk”一般在Makefile的开头,定义一些包的基本信息。
软件包的信息均以“PKG_”开头,其意思和作用如下:
PKG_NAME:软件包名称,将在menuconfig和ipkg可以看到。
PKG_VERSION:软件版本号。
PKG_RELEASE:Makefile的版本号
PKG_SOURCE:源代码的文件名。
PKG_SOURCE_URL:源代码的下载网站位置。@SF表示在sourceforge网站,@GNU表示在GNU网站,还有@GNOME、@KERNEL。获取方式可以为:git、svn、cvs、hg、bzr等。有关下载方法可参考$(INCLUDE_DIR)/download.mk和$(SCRIPT_DIR) /download.pl。由于本文使用的是自己开发的代码,所以没有此项。
PKG_MD5SUM:源代码文件的效验码。用于核对软件包是否下载正确。
PKG_CAT:源代码文件的解压方法。包括zcat, bzcat, unzip等。
PKG_BUILD_DIR:软件包编译目录。它的父目录为$(BUILD_DIR)。如果不指定,默认为$(BUILD_DIR)/$( PKG_NAME)-$( PKG_VERSION)。
还有一些有关源代码的定义如下:
PKG_SOURCE_SUBDIR
PKG_SOURCE_PROTO
PKG_SOURCE_MIRROR
PKG_MIRROR_MD5SUM
PKG_SOURCE_VERSION 2.2.2、第17行“include $(INCLUDE_DIR)/package.mk”
“include $(INCLUDE_DIR)/package.mk”一般在软件包的基本信息完成后再引入,他定义了用户态软件包的规则。
编译包分为用户态和内核模块,用户态软件包使用Package,内核模块使用KernelPackage。“$(INCLUDE_DIR)/kernel.mk”文件对于软件包为内核时不可缺少,“$(INCLUDE_DIR)/package.mk”应用在用户态。接下来讲述用户态软件包。用户程序的编译包以“Package/”开头,然后接着软件名,在Package定义中的软件名可以与软件包名不一样,而且可以多个定义。
2.2.3、第19行”define Package/luci-app-szloogson
包的名称为”luci-app-szloogson“。
接下来定义的包括:
SECTION:包的类型,预留。
CATEGORY:分类,在menuconfig的菜单下将可以找到。
SUBMENU:包在make menuconfig的位置,此处即在”LuCi/3. Applications“下。
TITLE:用于软件包的简短描述,将显示在”make menuconfig“中。
DESCRIPTION:用于软件包的详细描述,已放弃使用。如果使用DESCRIPTION将会提示“error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description”。
URL:软件包的下载位置。
MAINTAINER:维护者选项。
DEPENDS:与其他软件的依赖。即如编译或安装需要其他软件时需要说明。如果存在多个依赖,则每个依赖需用空格分开。依赖前使用+号表示默认显示,即对象沒有选中时也会显示,使用@则默认为不显示,即当依赖对象选中后才显示。
2.2.4、第27行”define Package/luci-app-szloogson/description“
软件包的详细描述,取代前面提到的DESCRIPTION详细描述。此处定义的信息将显示在”make menuconfig“中。
2.2.5、第31行”define Build/Prepare“
编译准备方法,对于网上下载的软件包不需要再描述。对于非网上下载或自行开发的软件包必须说明编译准备方法。本文所用的准备方法就是首先创建软件包目录,然后将源码拷贝到刚刚创建的目录中。按OpenWrt的习惯,一般把自己设计的程序全部放在src目录下。
2.2.6、第36行"define Build/Configure”
Build/Configure:在Automake中需要进行“./configure”,所以本配置方法主要针对需要配置的软件包而设计,一般自行开发的软件包可以不在这里说明。本文设计的package由自己写makefile,所以此处没有定义。
2.2.7、第39行”define Build/Compile“
编译方法,没有特别说明的可以不予以定义。如果不定义将使用默认的编译方法Build/Compile/Default。
自行开发的软件包可以考虑使用下面的定义:
点击(此处)折叠或打开
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
$(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS) -I$(LINUX_DIR)/include"
Endef
本文此处指定了交叉编译器和体系结构。
2.2.8、第46行”define Package/luci-app-szloogson/install“
软件包的安装方法,包括一系列拷贝编译好的文件到指定位置。调用时会带一个参数,就是嵌入系统的镜像文件系统目录,因此$(1)表示嵌入系统的镜像目录。一般可以采用下面的方法:
点击(此处)折叠或打开
define Package/luci-app-szloogson/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/gsc3280_led $(1)/usr/sbin/
endef
INSTALL_DIR、INSTALL_BIN在”$(TOPDIR)/rules.mk“文件中定义,所以本Makefile必须引入$(TOPDIR)/rules.mk文件。
INSTALL_DIR :=install -d -m0755:创建所属用戶可读写、执行,其他用戶可读可执行的目录。
INSTALL_BIN:=install -m0755:编译好的文件到镜像文件目录。
安装文件放在files子目录下,不要与源代码文件目录“src”混在一起,以提高可读性。
如果用户态软件在boot时要自动运行,则需要在安装方法说明中增加自动运行的脚本文件安装和配置文件安裝方法。
例如:
点击(此处)折叠或打开
define Package/luci-app-szloogson/install
#install shell
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/loogson.init $(1)/etc/init.d/loogson
#install config
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/loogson.config $(1)/etc/config/loogson
endef
使用清晰的文件扩展名,更方便安裝识别文件。
Package/$(PKG_NAME)/preinst
软件包安装前处理方法,使用脚本语言,因此定义的第一行需要下面的格式
#!/bin/sh:调用时带入的参数为嵌入式系统的镜像目录。
Package/$(PKG_NAME)/postinst:软件包安装后处理方法,使用脚本语言。
Package/$(PKG_NAME)/prerm:软件包删除前处理方法,使用脚本语言
Package/$(PKG_NAME)/postrm:软件包删除后处理方法,使用脚本语言
程序接下来安装luci文件。
2.2.9、第66行”$(eval $(call BuildPackage,luci-app-szloogson))“
完成前面定义后,必须使用eval函数实现各种定义。其格式为:
对于一般软件包:$(eval $(call Package,$(PKG_NAME)))
或对于内核模块:$(eval $(call KernelPackage,$(PKG_NAME)))
如果一个软件包有多个程序,例如:一个应用程序有自己的内核模块,上面使用的“PKG_NAME”需要灵活变通。eval函数可能设计多个。也可以当成多个软件包处理。
2.2.10、本文没有用到的
Package/$(PKG_NAME)/conffiles:本包安装的配置文件,一行一个。如果文件结尾使用“/”,则表示为目录。用于备份配置文件说明,在sysupgrade命令执行时将会用到。
2.3、内核模块包定义
Linux分为内核态和用户态。开发者开发的内核部分可以直接加入Linux的Kernel程序,也可以生成内核模块以便需要时装入内核。OpenWrt一般希望开发者生成内核模块,在Linux启动后自动装载或手工使用insmod命令装载。内核模块使用“KernelPackage”开头,其他与一般软件包基本相同。
在内核模块定义中增加“SUBMENU”表示子菜单位置,在“$(INCLUDE)/kernel.mk”对内核模块定义了CATEGORY为kernel modules,所以内核模块在menuconfig中的主菜单为kernel modules,然后有下一级子菜单$(SUBMENU)。在子菜单下可以看到以kmod-$( PKG_NAME)项目。
DEFAULT表示直接编入内核或产生内核模块,y表示直接编入内核,m表示产生内核模块。
AUTOLOAD表示自动装入内核,一般表示方法为:
AUTOLOAD:=$(call AutoLoad, $(PRIORITY),$(AUTOLOAD_MODS))
AutoLoad的第一个参数$(PRIORITY)为优先级,01为最优先,99为最后装载。有关自动装载可以在/etc/modules.d目录下看到,第二个参数$(AUTOLOAD_MODS)模块名,每个模块名以空格符分隔。即可同时装载多个内核模块。
在开发过程最好不要使用自动装载,需经过严格调试后再使用,可以减轻调试的工作量。
用户态的软件包中沒有内核模块的“AUTOLOAD”参数。如果软件需要在boot时自动运行,则需要在“/etc/init.d”增加相应的脚本文件。 脚本文件需要START参数,说明在boot时的优先级,如果在boot启动后再启动,则需要STOP参数。如果STOP参数存在,其值必须大于START。由“/etc/rc.d/S10boot”知道,装载内核模块的优先级为10,需要使用自己设计的内核模块的程序其START的值必须大于10。同样由“/etc/rc.d/S40network”知道,使用网络通信的程序其START的值必须大于40。
2.4、脚本文件
脚本文件需要start()和stop()两个函数,start()是执行程序,stop()是关闭程序。关闭程序一般需要执行killall命令。
在(一)中我们讨论了点击“应用”后执行的脚本文件在“/etc/init.d/loogson”目录下,程序如下:
点击(此处)折叠或打开
#!/bin/sh /etc/rc.common
# (C) 2014 openwrt.org
# add by Davied Huang <apple_guet@126.com>
START=50
LED_BIN="/usr/sbin/gsc3280_led"
control_board()
{
local ledstatus, lednum;
config_get_bool ledstatus $1 ledstatus
config_get lednum $1 lednum
echo "${lednum} ${ledstatus}"
${LED_BIN} ${lednum} ${ledstatus}
}
start() {
config_load loogson
config_foreach control_board controlboard
}
stop() {
config_load loogson
#config_foreach stop_instance controlboard
}
说明:
1) 在“start”函数中,首先使用“config_load”函数加载“/etc/config/”目录下的loogson配置文件。
2) “config_foreach”遍历"/etc/init.d/loogson"配置文件中的Section,并且执行"control_board"函数。
3) 在"control_board"函数中,使用“config_get_bool”获得操作LED的开关状态,config_get获得操作第几个LED。
4) "config_load"、“config_foreach”、“config_get”和“config_get_bool”等函数由其他脚本提供,可以直接使用。
5) 最后执行可执行文件,后面加上可执行文件需要的参数。该可执行文件的源码为:
点击(此处)折叠或打开
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<asm/ioctl.h>
int main(int argc,char *argv[])
{
int fd;
if (argc != 3) {
printf("wrong cmd!\n");
}
fd = open("/dev/led", O_RDWR);
if(fd == -1){
printf("open led failed!\n");
}
ioctl(fd, argv[1], argv[2]);
close(fd);
return 0;
}
三、参考资料
官方说明文档: http://luci.subsignal.org/trac/wiki/Documentation
LuCI上配置Makefile: http://luci.subsignal.org/trac/wiki/Documentation/Modules
CBI文档: http://luci.subsignal.org/trac/wiki/Documentation/CBI
Luci模块说明文档: http://luci.subsignal.org/trac/wiki/Documentation/ModulesHowTo
Luci类库的函数定义和使用说明: http://luci.subsignal.org/api/luci/index.html
UCI接口在脚本文件中的官方说明: http://wiki.openwrt.org/doc/devel/config-scripting
*******************************************************************************************
【一、LuCI配置界面开发的框架】
LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三层架构,同时其使用Lua脚本开发,所以开发LuCI的配置界面不需要编辑任何的Html代码,除非想自己单独去创建网页(View层),否则我们基本上只需要修改Model层就可以了。官方也有一个如何去创建模块的说明文档,虽然写的比较晦涩: http://luci.subsignal.org/trac/wiki/Documentation/ModulesHowTo
要为LuCI增加一个新模块,首先需要创建两个文件,一个位于Controller(/usr/lib/lua/luci/controller/)下,定义模块的入口;另一个位于Model(/usr/lib/lua/luci/model/cbi/)下,为配置模块实际的代码。
首先我们定义模块的入口,在/usr/lib/lua/luci/controller/下创建一个lua文件,类似如下:
- module("luci.controller.控制器名", package.seeall)function index()
- entry(路径, 调用目标, _("显示名称"), 显示顺序)
- end
第一行说明了程序和模块的名称,比如在controller/目录创建一个mymodule.lua,那么就可以写成“luci.controller.mymodule”,如果你的程序比较多,可能分为好几个模块,那么可以在controller下再常见一个子目录,比如controller/myapp/,那么就可以写成“luci.controller.myapp.mymodule”。
接下来的entry表示添加一个新的模块入口,官方给出了entry的定义,其中后两项都是可以为空的:
entry(path, target, title=nil, order=nil)
第一项是访问的路径,不过路径是按字符串数组给定的,比如路径按如下方式写“{"click", "here", "now"}”,那么就可以在浏览器里访问“http://192.168.1.1/cgi-bin/luci/click/here/now”来访问这个脚本。而通常我们希望为管理员菜单添加脚本,那么我们需要按如下方式编写“{"admin", "一级菜单名", "菜单项名"}”,系统会自动在对应的菜单中生成菜单项。比如想在“网络”菜单下创建一个菜单项,那么一级菜单名可以写为“network”。
第二项为调用目标,调用目标分为三种,分别是执行指定方法(Action)、访问指定页面(Views)以及调用CBI Module。
第一种可以直接调用指定的函数,比如点击菜单项就直接重启路由器等等,比如写为“call("function_name")”,然后在lua文件下编写名为function_name的函数就可以调用了。
第二种可以访问指定的页面,比如写为“template("myapp/mymodule")”就可以调用/usr/lib/lua/luci/view/myapp/mymodule.htm文件了。
而如果要编写配置页面,那么使用第三种方法无非是最方便的,比如写为“cbi("myapp/mymodule")”就可以调用/usr/lib/lua/luci/model/cbi/myapp/mymodule.lua文件了。
而title和order无非是针对管理员菜单来的,可以参考其他的lua文件来决定编写的内容。
这里我们创建/usr/lib/lua/luci/controller/njitclient.lua文件,定义我们的入口,代码如下:
- module("luci.controller.njitclient", package.seeall)function index()
- entry({"admin", "network", "njitclient"}, cbi("njitclient"), _("NJIT Client"), 100)
- end
【二、 用Lua和UCI接口开发LuCI配置模块 】
我们要做的实际上就是希望能将用户名、密码等信息存储在路由器文件中,同时路由器开机时能根据设定的配置自动运行njit-client,同时我们还希望能动态的禁用和启用njit-client等等。所以最方便的方式就是使用CBI Module,上一节我们也添加了这个调用,那么接下来我们就要根据上边写的路径来创建 /usr/lib/lua/luci/model/cbi/njitclient.lua文件。
开发LuCI的配置模块有很多种方式,比较基本的可以用SimpleForm,就跟开发普通的Web应用类似,当然最方便的还是使用UCI(Unified Configuration Interface,统一配置接口)的方式,因为使用UCI接口可以使得在LuCI中可以无需考虑配置文件如何存储和读取(这种方式也会自动创建“保存&应用”、“保存”以及“复位”三个按钮),同时在Bash文件中也可以非常方便的存储和读取。
对于使用UCI的方式,我们首先需要创建对应的配置文件(如果配置文件不存在的话,访问配置页面将会报错),格式即为linux配置文件的格式,文件需要存储在/etc/config,比如文件路径为“/etc/config/njitclient”,内容如下:
config login option username '' option password '' option ifname 'eth0' option domain ''
然后我们要在CBI Module的lua文件中首先需要映射与存储文件的关系,比如:
m = Map("配置文件文件名", "配置页面标题", "配置页面说明")
第一个参数即为配置文件存储的文件名,不包含路径,比如按上述创建的话,应该写为“njitclient”,而第二与第三个参数则是用在来页面上显示的,比如如下所示的图:
接下来需要创建与配置文件中对应的Section,Section分为两种,NamedSection和TypedSection,前者根据配置文件中的Section名,而后者根据配置文件中的Section类型,这里我们使用后者,代码如下。同时我们设定不允许增加或删除Section(“.addremove = false”),以及不显示Section的名称(“.anonymous = true”)。
s = m:section(TypedSection, "login", "") s.addremove = falses.anonymous = true
接下来我们需要创建Section中不同内容的交互(创建Option),常见的比如有Value(文本框)、ListValue(下拉框)、Flag(选择框)等等,详细的可以参考官方的文档: http://luci.subsignal.org/trac/wiki/Documentation/CBI
创建Option的过程非常简单,而且创建后系统会无需考虑读取以及写入配置文件的问题,系统都会自动处理。但是根据上述的要求,我们在应用配置以后可能希望启用、禁用或重新启动njit-client,所以我们还需要在页面最后判断用户是否点击了“应用”按钮,这里与编写asp网页等都是相同的,我们可以通过如下的代码判断是否点击了“应用”按钮:
- local apply = luci.http.formvalue("cbi.apply")if apply then
- --[[
- 需要处理的代码 ]]--end
由于剩余的代码都非常简单,所以所以这部分的全部代码见下:
1 require("luci.sys") 2 3 m = Map("njitclient", translate("NJIT Client"), translate("Configure NJIT 802.11x client.")) 4 5 s = m:section(TypedSection, "login", "") 6 s.addremove = false 7 s.anonymous = true 8 9 enable = s:option(Flag, "enable", translate("Enable"))10 name = s:option(Value, "username", translate("Username"))11 pass = s:option(Value, "password", translate("Password"))12 pass.password = true13 domain = s:option(Value, "domain", translate("Domain"))14 15 ifname = s:option(ListValue, "ifname", translate("Interfaces"))16 for k, v in ipairs(luci.sys.net.devices()) do17 if v ~= "lo" then18 ifname:value(v)19 end20 end21 22 local apply = luci.http.formvalue("cbi.apply")23 if apply then24 io.popen("/etc/init.d/njitclient restart")25 end26 27 return m
其中Luci全部类库的函数定义和使用说明可以参考如下地址: http://luci.subsignal.org/api/luci/index.html
【三、在Bash文件中调用UCI接口】
上边我们已经完成了LuCI配置界面的开发,在配置界面中我们已经能读取并保存配置文件了。接下来我们要编写/etc/init.d/njitclient脚本,使程序最终能运行起来。关于UCI接口在脚本文件中的官方说明可以参考: http://wiki.openwrt.org/doc/devel/config-scripting
要使用UCI调用脚本,首先第一步需要读取配置文件,命令为“config_load 配置文件名”,比如我们可以这样读入刚才的配置文件:
config_load njitclient
接下来要遍历配置文件中的Section,可以使用“config_foreach 遍历函数名 Section类型”,例如我们可以这样:
config_foreach run_njit login
然后我们去编写名为“run_njit”的函数,在这个函数中,我们可以使用“config_get 变量名 Section名 Section参数名”获取变量的值,或者使用“config_get_bool 变量名 Section名 Section参数名”获取布尔型的值。所以全部的代码见下:
1 #!/bin/sh /etc/rc.common 2 START=50 3 4 run_njit() 5 { 6 local enable 7 config_get_bool enable $1 enable 8 9 if [ $enable ]; then10 local username11 local password12 local domain13 local ifname14 15 config_get username $1 username16 config_get password $1 password17 config_get domain $1 domain18 config_get ifname $1 ifname19 20 if [ "$domain" != "" ]; then21 njit-client $username@$domain $password $ifname &22 else23 njit-client $username $password $ifname &24 fi25 26 echo "NJIT Client has started."27 fi28 }29 30 start()31 {32 config_load njitclient33 config_foreach run_njit login34 }35 36 stop()37 {38 killall njit-client39 killall udhcpc40 41 echo "NJIT Client has stoped."42 }
【四、 编译开发的程序 】
如果按上述内容创建好上述4个文件,那么配置页面和程序就能在OpenWrt上运行起来了。但是如果要想把自己写的程序打包,还需要创建OpenWrt的Makefile来使用OpenWrt的SDK进行编译。
关于LuCI上配置Makefile的官方说明可以见这个地址: http://luci.subsignal.org/trac/wiki/Documentation/Modules
无非就是定义包的名称(PKG_NAME)、版本和生成次数(PKG_VERSION、PKG_RELEASE)、在menuconfig中的分类说明等(define Package/luci-app-njitclient)以及安装时进行的操作(define Package/luci-app-njitclient/install)等等。其中安装的文件分为三种,分别是配置文件、可执行文件以及其他数据文件,其中配置可执行文件时,会自动加入执行权限的,所以不需要额外进行处理。Makefile全部的代码见下:
1 include $(TOPDIR)/rules.mk 2 3 PKG_NAME:=luci-app-njitclient 4 PKG_VERSION=1.0 5 PKG_RELEASE:=1 6 7 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) 8 9 include $(INCLUDE_DIR)/package.mk10 11 define Package/luci-app-njitclient12 SECTION:=luci13 CATEGORY:=LuCI14 SUBMENU:=3. Applications15 TITLE:=NJIT 802.1X Client for LuCI16 PKGARCH:=all17 endef18 19 define Package/luci-app-njitclient/description20 This package contains LuCI configuration pages for njit8021xclient.21 endef22 23 define Build/Prepare24 endef25 26 define Build/Configure27 endef28 29 define Build/Compile30 endef31 32 define Package/luci-app-njitclient/install33 $(INSTALL_DIR) $(1)/etc/config34 $(INSTALL_DIR) $(1)/etc/init.d35 $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi36 $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller37 38 $(INSTALL_CONF) ./files/root/etc/config/njitclient $(1)/etc/config/njitclient39 $(INSTALL_BIN) ./files/root/etc/init.d/njitclient $(1)/etc/init.d/njitclient40 $(INSTALL_DATA) ./files/root/usr/lib/lua/luci/model/cbi/njitclient.lua $(1)/usr/lib/lua/luci/model/cbi/njitclient.lua41 $(INSTALL_DATA) ./files/root/usr/lib/lua/luci/controller/njitclient.lua $(1)/usr/lib/lua/luci/controller/njitclient.lua42 endef43 44 $(eval $(call BuildPackage,luci-app-njitclient))
接下来在编译目录下的package目录下创建一个文件夹,如njitclient,然后将所有的文件按目录复制到该目录下即可。之后配置好OpenWrt的交叉编译环境后就可以使用OpenWrt SDK进行编译了,由于这类文章较多,故不再赘述,可以参考相关链接3及之后的文章。
原文链接:http://www.tuicool.com/articles/zaUNfy