又到了成胖子^_^每周一博的时间了.最近在学习openwrt luci方面的知识,为了贯穿整个知识体系,练习题目为:javascript
经过页面配置周期性地往/tmp/addtest文件写入内容和时间戳
1.在web主页面的下拉菜单作一个按钮,进入设置页面;
2.两个设置项:输入的内容和周期;
3,读取/tmp/addtest中的内容并显示在页面上;css
代码已经开源,欢迎交流~html
这部分网上相关文章不少,也能够参见拙做java
首先回答一个问题:什么是Luci?
>LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三层架构,使用Lua脚本开发.linux
简单地说,Luci就是用来作openwrt的页面的.不一样于常见的html+css+javascript,Openwrt是用lua脚本语言开发的.git
怎么开发一个页面呢?github
要开发一个新的功能页面,开发者只要根据MVC框架写些简单的lua脚本,剩下的部分由openwrt为你自动完成.web
说到MVC框架了,什么是MVC框架呢?shell
MVC是model+view+controller的简写.为了便于开发,openwrt将实现不一样功能的lua脚本放在不一样的文件夹中.请看下图:
vim
什么是controller控制器?
咱们在这里设置功能在页面的位置,同时设置点击页面后,将要调用的功能.是要去Model模型读写配置数据呢?仍是要呈现一个静态页面,或者是直接执行lua脚本函数.
什么是model模型?
这里咱们经常使用的是,经过cbi模块和UCI(统一配置接口)进行交互.简单地说,就是咱们在这里将页面和路由器里面的配置关联起来,从而将页面的设置写到路由器当中.
什么是view视图?
这个应该是最容易理解的,就是呈现的页面的样式,有点相似于传统的html页面.
上面说到了UCI(Unified Configuartion Interface),这是什么龟?
openwrt将配置用统一的格式书写,放在规定的地方(/etc/config/),同时提供接口函数进行读取和设置.
若是还不太明白,接着向下看.若是有可能跟着我动动手,相信你很快就会掌握:)
咱们先看下最终效果图:
咱们在页面上面的System
下拉框的下面加了一个AddTest
按钮,下面有两个子选项:Set
和Info
.其中Set
用于选择是否开启功能,设置时间间隔和内容.Info
用于显示/tmp/addtest
文件中的内容.
首先,嗯~
你得有环境,得有电,有源码,编译过简单的ipk.若是没有,请回炉重造.
其次,创建相应的文件夹及文件.至于linux操做神马的,我相信你必定没有问题.
$mkdir -p ~/temp/addtest
$cd ~/temp/addtest
最终文件树形图
骨架已经有了,下面只须要往里面填肉了,是否是感受很快~
不要管为何要这样,咱们后面慢慢解释.
前面咱们提到,controller主要用于控制页面按钮位置,以及调用的功能.首先来编辑这个文件.
$vim ~/temp/addtest/files/usr/lib/lua/luci/controller/addtest.lua
代码以下:
module("luci.controller.addtest",package.seeall)
function index()
entry({"admin","system","addtest"},alias("admin","system","addtest","set"),_("AddTest"),99).index=true
entry({"admin","system","addtest","set"},cbi("addtest"),_("Set"),1)
entry({"admin","system","addtest","info"},call("action_info"),_("Info"),2)
end
function action_info()
if not nixio.fs.access("/tmp/addtest") then
return
end
local info = nixio.fs.readfile("/tmp/addtest")
luci.template.render("addtest_info",{info=info})
end
格式模板:
module("luci.controller.控制器名", package.seeall)
function index()
entry(路径, 调用目标, _("显示名称"), 显示顺序)
end
这个脚本文件能够分为3块:第1行,3~7行,9~16行
addtest.lua
文件,将模板中的控制器名替换为
addtest
便可.
{"admin","system","addtest"}
表示按钮的位置.
admin
表示咱们这个功能只有以管理员身份登陆页面才能够看到.
system
表示一级菜单名,
addtest
则是一级菜单下的子菜单.
alias("admin","system","addtest","set")
表示调用的功能.这个按钮没有独立的功能,而是将它关联到它的下一级子菜单
set
.
_("AddTest")
表示显示名称,可选.若是页面按钮想作成中文,能够在这里设置.
99
表示显示顺序的优先级,Luci根据这个值为同一父菜单的全部子菜单排序.
{"admin","system","addtest","set"}
表示在
addtest
下再增长一个子选项
set
.
cbi("addtest")
表示调用cbi模块,这里将会调用到
/usr/lib/lua/luci/model/cbi/addtest.lua
call("action_info")
表示执行指定方法,这里将会调用咱们下面写的
acttion_info
函数.
entry
第二个参数调用目标.咱们还有一个
template
没有涉及,它表示访问指定页面.好比
template(addtest_info)
将会直接访问
/usr/lib/lua/luci/view/addtest_info.htm
.
nixio
接口写了一个简单的函数,首先判断文件是否存在,而后读取其中的内容赋值给变量
info
,最后访问指定页面
/usr/lib/lua/luci/view/addtest_info.htm
,同时将变量
info
传递过去.
UCI是openwrt的配置管理机制,它将配置统一放到/etc/config
文件夹下.详细地介绍请参考这里.
下面来编辑这个文件
$vim ~/temp/addtest/files/etc/config/addtest
代码以下:
config arguments
option interval ''
option content ''
Section开始语法: config '类型' '名字'
参数定义语法: option '键' '值'
列表定义语法: list '集合名字' '值'
简单解释下,咱们在/etc/config
下新建一个名为addtest
的配置文件,其中类型为arguments
,名字省略.有两个键,一个名为interval
用来存时间间隔.一个名为content
用来存准备周期性输入的内容.
在controller章节中,咱们提到cbi
会调用到model
文件夹中的addtest.lua
文件.下面咱们来编辑它.
$vim ~/temp/addtest/files/usr/lib/lua/luci/model/cbi/addtest.lua
代码以下:
m=Map("addtest",translate("Luci practice"),translate("fat cheng's test"))
s=m:section(TypedSection,"arguments","")
s.addremove=true
s.anonymous=false
s:option(Flag,"enable",translate("Enable"))
s:option(Value,"interval",translate("Interval"))
s:option(Value,"content",translate("Content"))
local apply=luci.http.formvalue("cbi.apply")
if apply then
io.popen("/etc/init.d/addtestd restart")
end
return m
下面咱们来解释下这个文件.
m = Map("配置文件文件名", "配置页面标题", "配置页面说明")
/etc/config/addtest
.这里就是创建与配置文件的联系.
true
,看看会发生什么.
s:option(交互形式,option键值,显示名称)
.
Save&Apply
,
Save
,
Reset
.咱们仅仅将配置写入
/etc/config
下对应的文件是不够的,咱们还但愿能够根据这个配置进行一些操做.
apply
按钮后,至关于在串口shell下输入
/etc/init.d/addtestd restart
上一节咱们已经能够读写配置了,怎么根据配置来进行操做呢?这是咱们这一节要谈的.咱们来编辑~/temp/addtest/files/etc/init.d/addtestd
这个文件.
代码以下:
#!/bin/sh /etc/rc.common
START=50
run_addtest()
{
local enable
config_get_bool enable $1 enable
if [ $enable ]; then
local interval
local content
config_get interval $1 interval
config_get content $1 content
addtest $interval $content
fi
}
start()
{
config_load addtest
config_foreach run_addtest arguments
}
stop()
{
result=`pidof addtest`
kill -9 $result
echo "addtest has stoped"
}
/etc/config/addtest
中的内容,而后根据是否打开开关在第15行将配置传递给可执行文件
addtest
,由它根据配置执行指定的操做.
config_get_bool 变量名 Section名 Section参数名
config_get 变量名 Section名 Section参数名
/etc/init.d/addtestd start
.首先使用
config_load 配置文件名
的方法载入配置文件,而后使用
config_foreach 遍历函数名 Section类型
的方法,遍历配置文件中的Section.
/etc/init.d/addtestd stop
.找到
addtest
这个进程的进程号,而后杀死它
/etc/init.d/addtestd restart
中的
restart
命令,在
/etc/rc.common
进行了定义,简单来说就是先执行了
stop
命令,再执行
start
命令.
$sudo chmod 755 ~/temp/addtest/files/etc/init.d/addtestd
.
前一节,咱们谈到run_addtest
调用可执行文件addtest
,如今咱们编辑这部份内容
$vim ~/temp/addtest/files/src/addtest.c
代码以下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int index;
for(index=0; index<10; index++)
{
FILE *fp=fopen("/tmp/addtest","at");
system("date >> /tmp/addtest");
fprintf(fp, "%s\n", argv[2]);
fclose(fp);
printf("interval=%d\n",atoi(argv[1]));
sleep( atoi(argv[1]) );
}
return 0;
}
这部分代码比较简短,咱们再也不解释.须要掌握的点有:
1.
argc
和argv[]
的使用方法
2.fopen
函数,fclose
函数以及fprintf
函数的使用方法
3.system
函数的使用方法
4.sleep
函数和atoi
函数的使用方法,argv[1]
的类型为char
须要转换为整型.
经过这个可执行文件,咱们周期性地将时间戳和内容写入了/tmp/addtest
文件.
最后咱们写一个简单的Makefile:
$vim $vim ~/temp/addtest/files/src/Makefile
代码以下:
addtest : addtest.o
$(CC) addtest.o -o addtest
addtest.o : addtest.c
$(CC) -c addtest.c
clean :
rm *.o addtest
上一节,咱们已经根据配置将指定的内容周期性地写入了/tmp/addtest
.在controller那一节,咱们的函数action_info
读取了/tmp/addtest
中的内容并访问指定页面/usr/lib/lua/luci/view/addtest_info.htm
,同时将读取的内容经过变量info
传递过去.
下面咱们来编辑这个页面,
$vim ~/temp/addtest/files/usr/lib/lua/luci/view/addtest_info.htm
代码以下:
<%+header%>
<h2><a id="content" name="content"><%:Addtest Info%></a></h2>
<div id="content_addtest_info">
<textarea readonly="readonly" wrap="off" rows="<%=info:cmatch("\n")+2%>" id="info"><%=info:pcdata()%></textarea>
</div>
<%+footer%>
这部分和传统的html
很相似,我主要是根据其余页面照猫画虎,不是很美观.有机会还要增强这个方面的学习.
不知不觉,咱们竟然已经将代码所有写完了,竟还有点依依不舍呢.下面咱们用一个Makefie
文件将它们打包生成一个ipk文件.
$vim ~/temp/addtest/Makefile
代码以下:
include $(TOPDIR)/rules.mk
PKG_NAME:=addtest
PKG_VERSION=1.0
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/addtest
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Addtest--print something to /var/addtest
endef
define Package/addtest/description
It's a test,print something to /var/addtest cyclicaliy
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/addtest/postinst
#!/bin/sh
rm -rf /tmp/luci*
endef
define Build/Configure
endef
define Build/Compile
$(call Build/Compile/Default)
endef
define Package/$(PKG_NAME)/install
$(CP) ./files/* $(1)/
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/addtest $(1)/bin
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
Makefile的解释,请参见拙做.咱们这里稍做补充.
/tmp
目录下运行,每次新加载luci模块后,须要执行
$rm -rf /tmp/luci*
.这里表示安装了ipk以后,将会自动执行删除命令,从新载入.
files
下的内容拷贝到路由器的文件系统中.这也是咱们为何要创建一开始那么复杂的目录树的缘由.
简直像裹脚布同样,又臭又长.不要说读了,我本身写的都快有点受不了了.读到这里的人真是辛苦了,下面到了咱们收获果实的时候了.
将文件拷贝到源码目录的package
目录下.其他部分,请参考拙做
$cp ~/temp/addtest ~/openwrt/package
把它拷贝到你的开发板中,试试看.
咱们固然但愿能够一次成功,不过世间不如意之事十之八九.我来谈谈我本身的调试方法.
src
部分
src
文件下有
Makefile
文件,你能够直接在编译机上执行
$make
生成可执行文件
addtest
,而后在编译机上
src
目录下执行
$./addtest 参数1 参数2
.最后记得执行
$make clean
.
luci
部分
$rm -rf /tmp/luci*
.最后从新载入设备页面.
不知不觉到了分手的时候,竟感受有些忧桑呢.
多多包含:)
除了官方文档以外,这两篇博客给我不少指导:
开发OpenWrt路由器上LuCI的模块, openwrt中luci学习笔记.
个人同事宁财神给咱们作了luci的框架介绍,同时在个人调试过程当中,给予我不少帮助.
最后感谢管工给出这样一个练习题,虽然很小巧,竟然能够贯通整个知识体系.我如今仍是为他的高屋建瓴感到惊叹.
在整篇文章学习完成后,咱们但愿能够回答如下几个问题:
1.MVC是什么?各部分有哪些功能? 2.怎么在页面上指定位置作出一个子页面. 3.怎么将配置写入到路由器中,又怎么读取? 4.页面怎么和可执行文件关联起来?或者通俗地说,页面点了一下,开发板怎么就执行了命令. 5.ipk怎么生成,安装过程当中发生了什么?