以京东签到服务插件为例,讲解OpenWrt插件的基本构成。

代码仓库地址:

https://github.com/jerrykuku/luci-app-jd-dailybonus

把代码clone到本地。

看本文之前,先看这篇《OpenWrt深入之编译框架分析》

环境搭建

为了方便编译测试,我先下载了lean版本的OpenWrt代码。

1git clone https://github.com/coolsnowwolf/lede

然后把luci-app-jd-dailybonus放到package/lean/目录下。

目录结构

先看看代码目录结构。

1234567891011121314151617181920212223242526272829303132333435.├── luasrc│   ├── controller│   │   └── jd-dailybonus.lua│   ├── model│   │   └── cbi│   │   └── jd-dailybonus│   │   ├── client.lua│   │   ├── log.lua│   │   └── script.lua│   └── view│   └── jd-dailybonus│   ├── cookie_tools.htm│   └── update_service.htm├── Makefile├── po│   └── zh-cn│   └── jd-dailybonus.po├── README.md├── relnotes.txt└── root ├── etc │   ├── config │   │   └── jd-dailybonus │   ├── init.d │   │   └── jd-dailybonus │   └── uci-defaults │   └── luci-jd-dailybonus ├── usr │   ├── lib │   │   └── node │   │   └── request //request库。这个是一个网络库。不管。 │   │   ├── CHANGELOG.md │   │   ├── index.js │   │   ├── lib //这个下面内容就非常多了。

上面看起来内容比较多。我们先只看最上层的目录。

1234567.├── luasrc //lua代码。是插件的逻辑代码。├── Makefile ├── po //国际化资源,只放了中文的。这个主要是给国人用的。├── README.md├── relnotes.txt└── root //这些内容会被拷贝覆盖系统的根目录。

所以,一个插件的基本目录又4个部分构成:

1234luasrc:主要代码Makefile:编译po:语言文件root:配置文件等。Makefile

这个是分析的入口。看看是怎样编译的。

一个插件本质上是一个package。

所以它的规则是服从package的规则。

一个package,可以进行的操作有:

123456789101112cleandownloadprepare compile update refresh prereq dist distcheck configure check check-depends

为了实现这些操作,需要在Makefile里对应进行实现。

实现可以留空。

12345define Build/Prepare #对应prepare操作。endefdefine Build/Compile #对应compile操作。endef第一部分1234567include $(TOPDIR)/rules.mk # 包含基本规则PKG_NAME:=luci-app-jd-dailybonus # 名字。作为luci组件,名字都是luci-app-xx这种格式。LUCI_PKGARCH:=all # 对所有架构都适用。因为是硬件无关的代码。PKG_VERSION:=0.8.8 # 版本号。x.y.z格式。PKG_RELEASE:=20201204 # 这个用日期也可以。include $(INCLUDE_DIR)/package.mk # 对于package的基本规则第二部分123456789101112131415161718define Package/luci-app-jd-dailybonus # 本package的基本定义。 SECTION:=luci # 属于luci这个section CATEGORY:=LuCI SUBMENU:=3. Applications TITLE:=Luci for JD dailybonus Script PKGARCH:=all DEPENDS:=+node +wgetendefdefine Build/Prepareendefdefine Build/Compileendefdefine Package/$(PKG_NAME)/conffiles/etc/config/jd-dailybonusendefpackage定义

define Package下面可以有哪些属性呢?这些属性有具体代表了什么呢?

1234567SECTION:package的类型。CATEGORY :在menuconfig里属于哪个菜单。TITLE :在menuconfig里看到的菜单项。URL:到哪里去找这个package。DEPENDS :依赖项。PKGARCH :一般给all。

Package/conffiles

这个package安装的一组配置文件。一个文件一行。文件名字定格写,前面不要有空格或者tab。

Package/description

描述信息。

Build/Prepare

一组命令。用来解压和打补丁。

Build/Configure

怎样编译代码。大多数情况留空。

Build/Install (optional)安装。

Build/InstallDev (optional)

Build/Clean (optional)

make clean对应。

Package/install

Package/preinst安装前。

Package/postinst安装后。

Package/prerm删除前。

Package/postrm

删除后。

第三部分

这个部分是安装文件。

有4种 :

INSTALL_DIR:这个应该是会在没有对应目录的时候进行创建。

INSTALL_CONF:

INSTALL_BIN:这个应该会增加可执行权限。

INSTALL_DATA

12345678define Package/luci-app-jd-dailybonus/install $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./root/etc/config/jd-dailybonus $(1)/etc/config/jd-dailybonus $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./root/etc/init.d/* $(1)/etc/init.d/ # 其他剩余部分endef第四部分

就一句话,表示build这个package。

1$(eval $(call BuildPackage,luci-app-jd-dailybonus))config文件

这个是会被luci读写的。放在/etc/config目录下。

123456789config global option version ‘1.87’ option auto_run_time ‘1’ option auto_run ‘1’ option auto_update_time ‘1’ option auto_update ‘1’ option stop ‘0’ option failed ‘0’ option remote_url ‘https://cdn.jsdelivr.net/gh/NobyDa/Script/JD-DailyBonus/JD_DailyBonus.js’luasrc目录

这个下面的lua写的CGI代码。

用来处理网页数据逻辑的。

下面3个目录,model、view、controller。典型的mvc架构。

123456789101112├── controller│   └── jd-dailybonus.lua├── model│   └── cbi│   └── jd-dailybonus│   ├── client.lua│   ├── log.lua│   └── script.lua└── view └── jd-dailybonus ├── cookie_tools.htm └── update_service.htm

先看view。这个是我们可以看的到的部分。

打开cookie_tools.htm文件。

里面有这样的内部。这个是很明显的模板语法。

12

用谷歌搜索“openwrt cbi valueheader”

model下面的lua脚本,就是描述我们看到的配置界面的元素的。

lua脚本里,会包含一些html文件。

例如这样:

123o = s:option(DummyValue, “”, “”)o.rawhtml = trueo.template = “jd-dailybonus/cookie_tools”

就把cookie_tools.htm文件包含进来了。

而cookie_tools.htm里,就描述了3个按钮。

对应这部分:

CBI

我们得先了解一下CBI这个概念。

我们所谓的OpenWrt插件,实际上是LUCI插件。

LUCI是OpenWrt的界面组件。Luci是 Lua ConfigurationInterface的简称,意在OpenWrt整个系统的配置集中化。

使用lua编写。

CBI 是什么缩写呢?官方都没有看到描述。

CBI首先是lua文件,它的作用是描述UCI配置文件结构。

CBI解析器把lua文件转成html文件,在luci界面上进行显示。

所有 CBI 模型文件必须返回类型为 luci.cbi.Map 的对象。

就把CBI理解为一种模型,它可以包含一些控件。

而model中有一个特殊的模块叫做CBI,被称为LuCI中最酷的功能,该模块的功能是方便的对一个配置文件进行修改。

这3个标签页面,就跟model目录下3个lua文件一一对应。

控件类型有

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152TypedSection 一组配置section。NamedSection 一个section。Node 配置节点?抽象类。 Node = class()Template 一个简单的模板元素。 Template = class(Node)Map 一个map,描述了config配置文件。Compoud 名字是复合的意思。是一个容器。Delegator node控制器。SimpleForm 简单的non-uci表单。Form Form = class(SimpleForm)AbstractSection 抽象section。是NamedSection和TypedSection的父类。SimpleSection 简单section。Table 表格?AbstractValue 抽象值。Value 值。DummyValue 空值。Flag 二值状态。ListValue 列表值。MultiValue 多值。StaticList 静态List。DynamicList 动态List。TextValue 文本值。Button 按钮。FileUpload 文件上传。FileBrowser 文件浏览。Page 页。

编写测试方法

最简单的方法,是直接在路由器的目录下写代码。

但是这样只能用vim来写代码。

也可以在电脑上写好了。然后拷贝到路由器的目录上。

或者是把路由器的目录映射到本地,这样就可以用vscode来写了。

model是放在这个路径下。/usr/lib/lua/luci/model/cbi/jd-dailybonus

view是这个路径:/usr/lib/lua/luci/view/jd-dailybonus

controller是这个路径:/usr/lib/lua/luci/controller

当前OpenWrt最简单的办法,还是ftp的方式。

我把整个根目录都让ftp可以访问。

本来samba是更好的方式。但是目前不知道为什么总是无法访问。

官方文档里,推荐的方式是:通过支持scp或者sftp协议的IDE来直接在远程路由器上改代码。

vscode是支持这种方式的。

vscode远程开发OpenWrt luci插件

首先确保OpenWrt上openssh-sftp-server是安装的。

如下图所示。没有安装的话,搜索安装就好了。

image-20201208093707852

vscode里安装lua语言插件。搜索lua,安装第一个就好。

然后是在vscode里安装sftp插件。安装这个就好了。

image-20201208093930837

然后是配置,vscode按住ctrl+shift+p这3个按键,打开命令面板。输入sftp,选择“SFTP: config”进行配置。

image-20201208094234189

写入如下内容:

image-20201208094508800

我只取用/usr/lib/lua下面的部分,不然东西太多了。没有必要。

host要改成自己的OpenWrt的真实ip地址。

然后在资源管理器这个窗口里右键,选择“Sync Remote->Local”。把OpenWrt上的文件同步到本地。谈弹出窗口让你输入密码的。

image-20201208094612688

同步很快,因为内容不多。

image-20201208094759682

vscode可以正常解析luci的模块,并帮助我们进行代码提示和补全,非常好。

为了操作更加无缝,我们可以在vscode里打开终端,然后ssh连接到OpenWrt上。这样我们操作就很方便了。

image-20201208094952759

为了避免LUCI缓存导致修改不能及时生效,需要关闭luci的缓存。

在OpenWrt的命令行上执行:

12uci set luci.ccache.enable=0 #注意等于号左右不能有空格。uci commit luci

编写测试完毕后,我们可以把写的代码拷贝单独形成一个目录,改名为luci-app-xx。然后上传到github,这样就可以分享给其他人使用了。

HelloWorld

使用上面搭建好的开发环境,我们可以写一个HelloWorld程序。

在controller目录下,新建myapp目录,在myapp目录下,新建mymodule.lua文件。

如下图。

image-20201208110334375

mymodule.lua内容如下:

123456789101112131415module(“luci.controller.myapp.mymodule”, package.seeall)function index() entry( {“click”, “here”, “now”}, call(“action_tryme”), “click here”, 10 ).dependent = falseendfunction action_tryme() luci.http.prepare_content(“text/plain”) luci.http.write(“hello, openwrt luci”)end

然后我们保存文件,这样就自动保存到了OpenWrt里。

然后我们访问http://192.168.1.2/cgi-bin/luci/click/here/now

可以看到我们写的内容了。

image-20201208110532508

然后我们写view部分的。

在luci/view目录下,新建myapp-mymodule目录,在该目录下,新建helloworld.htm文件。

内容如下:

123456

然后在mymodule.lua里加上一个entry:

123456entry( {“my”, “new”, “template”}, template(“myapp-mymodule/helloworld”), “Hello World”, 20).dependent = false

然后访问:http://192.168.1.2/cgi-bin/luci/my/new/template

image-20201208111239968

然后看看cbi的。

在luci/model/cbi目录下新建myapp-mymodule目录,该目录下新建netifaces.lua文件。

这个用来设置网络接口。

12345678910111213141516171819202122232425262728293031323334353637m = Map(“network”, “Network”)s = m:section(TypedSection, “interface”, “Interfaces”)s.addremove = true — 允许用户创建和删除网络接口function s:filter(value) return value ~= “loopback” and valueends:depends(“proto”, “static”)s:depends(“proto”, “dhcp”)p = s:option(ListValue, “proto”, “Protocol”)p:value(“static”, “static”)p:value(“dhcp”, “DHCP”)p.default = “static”s:option(Value, “ifname”, “interface”, “the physical inteface to be used”)s:option(Value, “ipaddr”, “ip”, “IP Address”)s:option(Value, “netmask”, “Netmask”):depends(“proto”, “static”)mtu = s:option(Value, “mtu”, “MTU”)mtu.optional = truedns = s:option(Value, “dns”, “DNS-Server”)dns:depends(“proto”, “static”)dns.optional = truefunction dns:validate(value) return value.match(“[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+”)endgw = s:option(Value, “gateway”, “Gateway”)gw:depends(“proto”, “static”)gw.rmempty = truereturn m

然后增加一个entry

123456entry( {“myadmin”, “network”, “interfaces”}, cbi(“myapp-mymodule/netifaces”), “Network interfaces”, 30).dependent = false

访问:

http://192.168.1.2/cgi-bin/luci/myadmin/network/interfaces

image-20201208114404342

上面写的代码较多。我们分析一下。

总体上,是一个返回一个Map实例。

这个map里,有一个section。这个section对应interface这个配置。

可以看到我们当前的机器上,interface有3个,

image-20201208114654326

123m = Map(“network”, “Network”)s = m:section(TypedSection, “interface”, “Interfaces”)s.addremove = true — 允许用户创建和删除网络接口

Map(“network”, “Network”)

参数1:表示对应/etc/config/network这个文件。

参数2:表示页面最上面显示的字符串。

这个m:section的参数2是对应config里的interface这种option。

参数3是在界面上显示的字符串Interface。

s.addremove, 这个让界面上有添加和删除按钮,用来增加和删除interface的。

image-20201208115310245

123function s:filter(value) return value ~= “loopback” and valueend

这个就很明显,是过滤掉loopback接口。

12s:depends(“proto”, “static”)s:depends(“proto”, “dhcp”)

这个是需要interface的proto这个option满足static或者dhcp这两种情况之一。

image-20201208115619242

经过上面的过滤,现在只剩下lan这个符合要求。

1234p = s:option(ListValue, “proto”, “Protocol”)p:value(“static”, “static”)p:value(“dhcp”, “DHCP”)p.default = “static”

这个表示一个选项。还是ListValue类型。

效果是这样:

image-20201208115723567

123s:option(Value, “ifname”, “interface”, “the physical inteface to be used”)s:option(Value, “ipaddr”, “ip”, “IP Address”)s:option(Value, “netmask”, “Netmask”):depends(“proto”, “static”)

效果是

image-20201208115853410

option可以有4个参数。

参数1:类型。

参数2:这个是对应config文件里的option的名字。

参数3:前面的label。

参数4:可选。如果有,是下面的帮助信息。

options后面还可以跟depends。

HelloWorld2

我们现在在网络标签下,新增一个我的配置。

名字就叫mycbi。

先在/etc/config/目录下,新建mycbi文件。

内容如下:

123config ‘MySection’ ‘mycbi’ option ‘username’ ‘yourname’ option ‘password’ ‘yourpass’

然后新建目录/usr/lib/lua/luci/model/cbi/mycbi-model

在这个目录下,新建文件mycbimodule.lua。写入下面的内容:

1234567m = Map(“mycbi”, “mycbi conf change interface”)s = m:section(TypedSection, “MySection”)s.addremove = trues:option(Value, “username”, “Name:”)key = s:option(Value, “password”, “Password:”)key.password = truereturn m

然后在/usr/lib/lua/luci/controller目录下,新建mycbi.lua文件。

内容如下:

123456789101112module(“luci.controller.mycbi”, package.seeall)function index() entry( { “admin”,”network”,”mycbi_change” }, cbi(“mycbi-model/mycbimodule”), “Change my conf”, 30 ).dependent = falseend

view不需要写。因为我们是作为network的一部分的。

效果是这样:

image-20201208131540596

从京东插件里总结

看了基本的插件写法,我们看看实际的插件。

京东插件,作为一个有实用价值的插件,可以学习参考一下。

首先看controller里的jd-dailybonus.lua。这个相当于入口。

先看index函数。

如果没有配置文件,则直接返回。

123if not nixio.fs.access(“/etc/config/jd-dailybonus”) then return end12entry({“admin”, “services”, “jd-dailybonus”}, alias(“admin”, “services”, “jd-dailybonus”, “client”), _(“JD-DailyBonus”), 10).dependent = true — 首页 entry({“admin”, “services”, “jd-dailybonus”, “client”}, cbi(“jd-dailybonus/client”),_(“Client”), 10).leaf = true — 基本设置

这2个entry等效。

可以跟一般网站的/和/index.html等效这种情况进行类比。

效果是:

访问http://192.168.1.2/cgi-bin/luci/admin/services/jd-dailybonus

等效于访问:http://192.168.1.2/cgi-bin/luci/admin/services/jd-dailybonus/client

一个是alias,一个是cbi。

这个都是在admin下的services下面。这个就决定了在界面上的位置。

alias、cbi这些,都是luci/dispatcher.lua里的函数。

我们可以通过vscode进行代码跳转查看。

12345678910function alias(…) local req = {…} return function(…) for _, r in ipairs({…}) do req[#req+1] = r end dispatch(req) endend123456789function cbi(model, config) return { type = “cbi”, post = { [“cbi.submit”] = “1” }, config = config, model = model, target = _cbi }end

entry还有2个是form类型。

实际表现是2个文本框的样式。一个是脚本的内容,一个是日志。

还有3个call,调用3个函数:执行京东签到、检查脚本更新、执行更新。

然后看model/cbi/jd-dailybonus下的文件。

client.lua:这个是返回一个Map。是一个常规的界面。

log.lua和script.lua:这2个都是返回一个SimpleForm。跟上面controller里写的的form是对应的。

script.lua访问的文件是/usr/share/jd-dailybonus/JD_DailyBonus.js

你可以在这里进行脚本的修改。

如果要写自己的插件,仿照jd-dailybonus这个来写就好了。

uci api

uci是一个C语言写的小工具。

用来统一处理OpenWrt里所有的配置文件。

uci处理的配置文件统一放在/etc/config目录下。

uci提供的价值是:传统Linux的配置文件分散在不同的目录下,而且配置文件的语法规则不统一,处理起来非常不方便。

这里列出了所有的uci配置文件。

https://oldwiki.archive.openwrt.org/doc/uci

uci的组成:

可执行文件:

/sbin/uci:二进制文件

/lib/config/uci.sh:包装脚本。里面封装了一些函数,方便在脚本里使用。

库文件:

/lib/libuci.so

/usr/lib/lua/uci.so:lua接口。

在lua里使用

123456789101112131415161718192021222324252627282930313233require(“uci”)x = uci.cursor() — 取得游标x = uci.cursor(nil, ‘/var/state’)–取得状态–下面就是基本操作–getx:get(“network”, “lan”, “proto”)–拿到/etc/config/network里lan的protooption的值。–setx:set(“network”, “lan”, “proto”, “dhcp”)–设置为dhcp方式–设置多个值。x:set(“system”, “ntp”, “server”, { “0.openwrt.pool.ntp.org”, “1.openwrt.pool.ntp.org” })–删除x:delete(“network”, “wan6”)–添加x:add(“network”, “switch”)–添加一个section,带类型的x:set(“network”, ‘wan6’, “interface”)–添加一个section,名字叫wan6,类型是interface。–遍历x:foreach(“system”, “led”, function(s) for key,value in pair(s) do print(key “:” tostring(value)) end end )–移动顺序x:reorder(“network”,”wan6″, 0)–把wan6放到最前面。–丢弃配置x:revert(“config”)–config表示/etc/config目录–提交配置x:commit(“config”)luci模块分类

luci模块分为下面几类:

应用i18nlibsmodulesthemes

写一个模块,你首先需要理解luci的分发处理的原理。

luci使用了一个分发树(dispatching tree),这个树会通过执行每个controller的index函数来构造。

要把一个函数注册到分发树里,你需要使用luci.dispatcher模块里的entry函数。

entry函数有4个参数:

1entry(path, target, title=nil, order=nil)

path:是一个table,描述了在分发树里的位置。例如:{“aa”, “bb”, “cc”}表示aa.bb.cc。

target:描述了当前用户请求当前节点的时候,会产生的行为。行为有3种最主要的,luci默认帮我们定义好了,是call、template、cbi这3个行为。

title:可选。表示用户在菜单里可以看到的标题。

order:是一个数字。表示在菜单里排放的位置。

可以对entry进行进一步的操作。

entry(xx).yy

yy可以是:

12345678i18n: 定义哪个翻译文件应该默认被使用。dependent: 如果父节点丢失了,保证不被外面调用。leaf: 表示自己的叶子节点,不要进入传递请求了。sysauth: 需要授权。luci接口测试

在/usr/lib/lua目录下,新建一个test.lua文件。

把luci\view\admin_status\index.htm里用到的接口,单独拿出来测试。

1234567891011121314151617181920212223local util = require(“luci.util”)local sysinfo = util.ubus(“system”, “info”)tab=” “function print_table(t, i) local indent =”” — i缩进,当前调用缩进 for j = 0, i do indent = indent .. tab end for k, v in pairs(t) do if (type(v) == “table”) then — type(v) 当前类型时否table 如果是,则需要递归, print(indent .. “”) print_table(v, i + 1) — 递归调用 print(indent .. “”) else — 否则直接输出当前值 print(indent .. “”) end endendprint_table(sysinfo, 0)

运行输出:

12345678910111213141516171819

大部分的信息,都是luci.sys.exec来得到的。

参考资料

1、Creating packages

https://openwrt.org/docs/guide-developer/packages

2、CBI

https://github.com/openwrt/luci/wiki/CBI

3、luci官方文档

https://github.com/openwrt/luci/wiki/Documentation

4、Openwrt中luci配置页面cbi小记

https://www.cnblogs.com/yangykaifa/p/7117775.html

5、Openwrt:LuCI之CBI(二)

https://blog.csdn.net/qq_28812525/article/details/103881723#t0

6、【OpenWRT之旅】如何自定义一个配置文件的设置界面

https://www.cnblogs.com/gnuhpc/p/3293204.html

7、OpenWRT – WEB界面开发思路和基本方法

https://www.cnblogs.com/h2zZhou/p/10357311.html

8、

https://github.com/openwrt/luci/wiki/DevelopmentEnvironmentHowTo

9、使用VSCODE远程开发openwrt luci插件

https://blog.csdn.net/gw826943555/article/details/104234226

10、Reference: LuCI Modules

https://github.com/openwrt/luci/wiki/Modules

11、

https://oldwiki.archive.openwrt.org/doc/techref/uci#api


比丘资源网 » OpenWrt插件之入门

发表回复

提供最优质的资源集合

立即查看 了解详情