Openwrt LuCI模塊練習詳細步驟


前言

又到了成胖子^_^每週一博的時間了.最近在學習openwrt luci方面的知識,爲了貫穿整個知識體系,練習題目爲:javascript

經過頁面配置週期性地往/tmp/addtest文件寫入內容和時間戳
1.在web主頁面的下拉菜單作一個按鈕,進入設置頁面;
2.兩個設置項:輸入的內容和週期;
3,讀取/tmp/addtest中的內容並顯示在頁面上;css

代碼已經開源,歡迎交流~html


知識準備

源碼編譯及ipk生成

這部分網上相關文章不少,也能夠參見拙做java

LuCI

首先回答一個問題:什麼是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腳本放在不一樣的文件夾中.請看下圖:
MVC架構vim

什麼是controller控制器?

咱們在這裏設置功能在頁面的位置,同時設置點擊頁面後,將要調用的功能.是要去Model模型讀寫配置數據呢?仍是要呈現一個靜態頁面,或者是直接執行lua腳本函數.

什麼是model模型?

這裏咱們經常使用的是,經過cbi模塊和UCI(統一配置接口)進行交互.簡單地說,就是咱們在這裏將頁面和路由器裏面的配置關聯起來,從而將頁面的設置寫到路由器當中.

什麼是view視圖?

這個應該是最容易理解的,就是呈現的頁面的樣式,有點相似於傳統的html頁面.

上面說到了UCI(Unified Configuartion Interface),這是什麼龜?

openwrt將配置用統一的格式書寫,放在規定的地方(/etc/config/),同時提供接口函數進行讀取和設置.

若是還不太明白,接着向下看.若是有可能跟着我動動手,相信你很快就會掌握:)


正文

咱們先看下最終效果圖:
最終效果
咱們在頁面上面的System下拉框的下面加了一個AddTest按鈕,下面有兩個子選項:SetInfo.其中Set用於選擇是否開啓功能,設置時間間隔和內容.Info用於顯示/tmp/addtest文件中的內容.

準備工做

首先,嗯~
你得有環境,得有電,有源碼,編譯過簡單的ipk.若是沒有,請回爐重造.
其次,創建相應的文件夾及文件.至於linux操做神馬的,我相信你必定沒有問題.

$mkdir -p ~/temp/addtest
$cd ~/temp/addtest

最終文件樹形圖
樹形圖
骨架已經有了,下面只須要往裏面填肉了,是否是感受很快~
不要管爲何要這樣,咱們後面慢慢解釋.


controller

前面咱們提到,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行

第1行
說明了模塊的名稱,本文在controller目錄下建立了 addtest.lua文件,將模板中的控制器名替換爲 addtest便可.
第3行
第3~7行定義按鈕的位置,調用的功能,顯示名稱.其中第3行和第7行是固定的模板格式,不須要修改
第4行
entry表示添加新的模塊.
第一個參數 {"admin","system","addtest"}表示按鈕的位置. admin表示咱們這個功能只有以管理員身份登陸頁面才能夠看到. system表示一級菜單名, addtest則是一級菜單下的子菜單.
第二個參數 alias("admin","system","addtest","set")表示調用的功能.這個按鈕沒有獨立的功能,而是將它關聯到它的下一級子菜單 set.
第三個參數 _("AddTest")表示顯示名稱,可選.若是頁面按鈕想作成中文,能夠在這裏設置.
第四個參數 99表示顯示順序的優先級,Luci根據這個值爲同一父菜單的全部子菜單排序.
第5行
第一個參數 {"admin","system","addtest","set"}表示在 addtest下再增長一個子選項 set.
第二個參數 cbi("addtest")表示調用cbi模塊,這裏將會調用到 /usr/lib/lua/luci/model/cbi/addtest.lua
第6行
第二個參數 call("action_info")表示執行指定方法,這裏將會調用咱們下面寫的 acttion_info函數.
備註
關於 entry第二個參數調用目標.咱們還有一個 template沒有涉及,它表示訪問指定頁面.好比 template(addtest_info)將會直接訪問 /usr/lib/lua/luci/view/addtest_info.htm.
9~16行
這裏使用lua語言調用 nixio接口寫了一個簡單的函數,首先判斷文件是否存在,而後讀取其中的內容賦值給變量 info,最後訪問指定頁面 /usr/lib/lua/luci/view/addtest_info.htm,同時將變量 info傳遞過去.
luci接口手冊
nixio接口手冊

UCI

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用來存準備週期性輸入的內容.


Model

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

下面咱們來解釋下這個文件.

第1行
模板 m = Map("配置文件文件名", "配置頁面標題", "配置頁面說明")
第一個參數:上一步咱們新建配置文件 /etc/config/addtest.這裏就是創建與配置文件的聯繫.
第二,三兩個參數,則是頁面的主標題和副標題.還不清楚的話,翻上去看看最終效果圖,看看它們在哪裏.
第3行
在一個配置文件中可能有不少Section,因此咱們須要建立與配置文件中咱們想要的Section的聯繫.
有兩種方式能夠選擇:NamedSection(name,type,title,description)和TypedSection(type,title,description),前者根據配置文件中的Section名,然後者根據配置文件中的Section類型.咱們選用了第二種.
第4行
設定不容許增長或刪除Section
第5行
設定顯示Section的名稱,這裏建議你能夠試試設定爲 true,看看會發生什麼.
7~9行
接着則是創建與Section中的option之間的聯繫.模板 s:option(交互形式,option鍵值,顯示名稱).
第一個參數:常見的交互形式有Value(文本框),ListValue(下拉框),Flag(選擇框).,不知道爲啥我打不開 官方文檔,這裏也能夠 參考
第二個參數表示在配置文件中的option的鍵值
第三個參數表示,你但願在頁面上呈現的名稱.
建立後開發者無需考慮讀取以及寫入配置文件的問題,系統會自動處理.
11~14行
系統會爲咱們在頁面上自動建立一些按鈕 Save&Apply, Save, Reset.咱們僅僅將配置寫入 /etc/config下對應的文件是不夠的,咱們還但願能夠根據這個配置進行一些操做.
這部分代碼的做用是,當你按下頁面的 apply按鈕後,至關於在串口shell下輸入 /etc/init.d/addtestd restart

init.d

上一節咱們已經能夠讀寫配置了,怎麼根據配置來進行操做呢?這是咱們這一節要談的.咱們來編輯~/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"
}
第1行
Linux 系統根據 「#!」 及該字串後面的信息肯定該文件的類型,表示這個文件須要由/bin/sh和/etc/rc.common來解釋執行.
第2行
表示啓動的優先級,這裏暫時用不到
4~17行
是一個函數,主要做用是讀取 /etc/config/addtest中的內容,而後根據是否打開開關在第15行將配置傳遞給可執行文件 addtest,由它根據配置執行指定的操做.
讀取配置的方法,我強烈推薦你閱讀 官方文檔,精煉而簡潔.
獲取布爾值類型: config_get_bool 變量名 Section名 Section參數名
獲取變量值: config_get 變量名 Section名 Section參數名
19~23行
對應於 /etc/init.d/addtestd start.首先使用 config_load 配置文件名的方法載入配置文件,而後使用 config_foreach 遍歷函數名 Section類型的方法,遍歷配置文件中的Section.
25~30行
對應於 /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.

src

前一節,咱們談到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.argcargv[]的使用方法
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

View

上一節,咱們已經根據配置將指定的內容週期性地寫入了/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很相似,我主要是根據其餘頁面照貓畫虎,不是很美觀.有機會還要增強這個方面的學習.


Makefile

不知不覺,咱們竟然已經將代碼所有寫完了,竟還有點依依不捨呢.下面咱們用一個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的解釋,請參見拙做.咱們這裏稍做補充.

26~29行
因爲luci會將模塊加載到 /tmp目錄下運行,每次新加載luci模塊後,須要執行 $rm -rf /tmp/luci*.這裏表示安裝了ipk以後,將會自動執行刪除命令,從新載入.
39行
$(1)是傳入的參數,表示系統鏡像目錄,你能夠將之視爲路由器最後的文件系統.因此這句的意思就是將咱們 files下的內容拷貝到路由器的文件系統中.這也是咱們爲何要創建一開始那麼複雜的目錄樹的緣由.

編譯&安裝

簡直像裹腳布同樣,又臭又長.不要說讀了,我本身寫的都快有點受不了了.讀到這裏的人真是辛苦了,下面到了咱們收穫果實的時候了.
將文件拷貝到源碼目錄的package目錄下.其他部分,請參考拙做

$cp ~/temp/addtest ~/openwrt/package

把它拷貝到你的開發板中,試試看.


調試方法

咱們固然但願能夠一次成功,不過世間不如意之事十之八九.我來談談我本身的調試方法.

src部分
src文件下有 Makefile文件,你能夠直接在編譯機上執行 $make生成可執行文件 addtest,而後在編譯機上 src目錄下執行 $./addtest 參數1 參數2.最後記得執行 $make clean.
luci部分
將ipk安裝到開發板後,能夠經過串口或者ssh的方式登陸開發板,而後直接在開發板中修改文件內容,再執行 $rm -rf /tmp/luci*.最後從新載入設備頁面.

尾記

不知不覺到了分手的時候,竟感受有些憂桑呢.

不足

  1. 我本身剛接觸學習,不免不少不足
  2. 頁面輸入沒有防呆機制

多多包含:)

感謝

除了官方文檔以外,這兩篇博客給我不少指導:
開發OpenWrt路由器上LuCI的模塊, openwrt中luci學習筆記.
個人同事寧財神給咱們作了luci的框架介紹,同時在個人調試過程當中,給予我不少幫助.
最後感謝管工給出這樣一個練習題,雖然很小巧,竟然能夠貫通整個知識體系.我如今仍是爲他的高屋建瓴感到驚歎.

Q&A

在整篇文章學習完成後,咱們但願能夠回答如下幾個問題:

1.MVC是什麼?各部分有哪些功能? 2.怎麼在頁面上指定位置作出一個子頁面. 3.怎麼將配置寫入到路由器中,又怎麼讀取? 4.頁面怎麼和可執行文件關聯起來?或者通俗地說,頁面點了一下,開發板怎麼就執行了命令. 5.ipk怎麼生成,安裝過程當中發生了什麼?

相關文章
相關標籤/搜索