關於OpenWrt的一知半解

OpenWrt開發學習總結:
一.學習準備:
a.學習lua腳本語言.
b.瞭解openwrt的文件架構,主要是luci的.
靜態的js,css,圖標文件,html文件放在路由器根目錄的www文件夾下
luci對應的controler,model,view的文件放在/usr/lib/lua/luci文件夾下,
源碼路徑爲openwrt-sdk\package\ramips\ui\luci-mtk\src
c.開發方式爲:利用mtk自帶samba功能,對路由器文件進行相應修改
(注意不可使用DW進行修改,否則會導致路由器無法啓動,推薦NotePad,
編譯時做make-menuconfig中開啓samba功能,然後再網頁頁面中添加,在菜單服務,
網絡共享中,Path設置爲"/"根目錄,勾選Allow guests,Create Mask設置爲777,
Directiory mask設置爲777,然後運行中填入"\\+IP"即可訪問路由器源文件,
進行修改);
注意:down下源碼後,需要對其進行更新,安裝luci等插件,執行兩個腳本即可:
./scripts/feeds update -a
./scripts/feeds install -a
利用samba功能修改代碼時,注意需要修改的文件將權限修改爲777.


二.開發學習總結:
1.luci:
Luci採用的是MVC架構的web框架.M是指moduel(有的源碼包是model),C指的是controller.V指的是view.Luci 
其實是Lua腳本語言和UCI統一配置接口的合稱.就是說原本openwrt就提供裏uci的api讓你可以很方便的修改
openwrt的配置文件,無論是創建,讀取,修改,刪除.而且uci命令可以植入shell腳本和c語言或者是lua腳本.
lua腳本由於比較小,運行速度是c語言的1/30左右.所以你會發現在/usr/lib/lua/luci下大多數都是lua腳本,
就是這些lua腳本生成我們所看見的web頁面.
我們主要修改/usr/lib/lua/luci/controller和/usr/lib/lua/luci/model/cbi下面的lua腳本就可以修改頁
面啦.當我們在瀏覽器訪問192.168.1.1的時候,我們首先是訪問/www/index.htm,然後它的<meta>標籤寫明是
立刻跳轉到/www/cgi-bin/luci(luci這個是一個腳本,是用於啓動/usr/lib/lua/luci下的lua腳本).這時有一
個應該是dispatcher.lua或者是index.lua的腳本進行對controller目錄下面的所有lua腳本進行檢查入口函數
並生成一個主頁面,包含了導航欄header列表,logout,底部footer.而controller就是定義入口函數entry()還
有調用cbi() model下的lua腳本或者是template()調用view下的htm模板.controller的作用就是進行調用控制
等等.就是說model是進行業務處理,例如當用戶輸入帳號密碼的時候,會把/etc/config下面的配置文件進行修
改.而如果controller調用的是view的htm模板就直接顯示出來.
Luci就是通過lua腳本進行對htm模板進行調用和組合人那後生成一個靜態的html文件讓我們可以訪問.




2.uhttpd:
這個是LuCI所在的Web Server。docroot在/www下邊,index-html指向了/cgi-bin/luci,注意這是相
對於docroot而言的路徑。
openwrt中利用它作爲web服務器,實現客戶端web頁面配置功能。對於request處理方式,採用的是cgi,而所用
的cgi程序就是luci。




3.模塊的初始化與調用.(以status模塊爲例)
模塊入口文件status.lua在目錄lua\luci\controller\admin下,
在index()函數中,使用entry函數來完成每個模塊函數的註冊:
--- entry(path, target, title=nil, order=nil) ---
第一個參數path是定義菜單的顯示(Virtual path);
第三個參數title是菜單的文本,_(“string”),國際化;
第四個參數order是是同級菜單下,此菜單項的位置,從小到大;
第二個target參數定義相應的處理方式(target)。處理方式如下:


a.alias是指向別的entry的別名
比如兩個entry需要指向同一個路徑的文件,只需要一個指明路徑,然後另一個指向該entry即可
eg:
entry({"admin", "status"}, alias("admin", "status", "overview"), _("Status"), 2).index = true
entry({"admin", "status", "overview"}, template("admin_status/index"), _("Overview"), 1)


b.from調用的某一個view,即lua\luci\view文件夾下的html文件,
eg:
entry({"admin", "system", "startup"}, form("admin_system/startup"), _("Startup"), 45)


c.cbi調用某一個model,即lua\luci\model\cbi下的文件
eg:entry({"admin", "status", "processes"}, cbi("admin_status/processes"), _("Processes"), 6)


d.call直接調用函數,即當前.lua文件下可使用的lua函數,
eg:
entry({"admin", "status", "syslog"}, call("action_syslog"), _("System Log"), 4)


function action_syslog()
local syslog = luci.sys.syslog()
luci.template.render("admin_status/syslog", {syslog=syslog})
end




4.關於CBI的調用:
 CBI模型是Lua文件描述UCI配置文件的結構和由此產生的HTML表單來評估CBI解析器,所有CBI luci.cbi.Map類型
的模型文件必須返回一個map對象,在cbi模塊中定義各種控件,Luci系統會自動執行大部分處理工作。其鏈接目錄
在lua\luci\model\cbi下
eg:
entry({"admin", "status", "processes"}, cbi("admin_status/processes"), _("Processes"), 6)
調用\lua\luci\model\cbi\admin_status\processes.lua來實現模塊。


流程爲以下:
a、映射UCI文件
b、生成section
c、生成option
其實質爲利用lua腳本生成對應的網頁控件,類似js生產html.這樣做方便腳本獲取網頁頁面的對應值
同時完成對應的操作,但是不利於界面的修改.
後面我們將介紹在html界面上使用lua的方法.二者各有優缺點.可自行選擇使用.
eg:
require("luci.sys")
--[[
 Map("配置文件文件名", "配置頁面標題", "配置頁面說明"),對應到配置文件/etc/config/testclient
]]--
m = Map("testclient", "the title of testclient", "the shuoming of testclient")
--[[
獲取所有類型爲login的section並生成html section
class可以是TypedSection表示根據類型獲取section
NamedSection表示根據名字獲取section
]]--
s = m:section(TypedSection, "login", "")
s.addremove = false
s.anonymous = true
--[[
生成option
class可以是:
Value:input控件
ListValue:下拉列表
Flag:選擇框
MultiValue:
DummyValue:純文本
TextValue:多行input
Button:按鈕
StaticList:
DynamicList:
下代碼爲每個section生成可選項控件,映射到proto字段
p= s:option(ListValue, “proto”, “Protocol”)
p:value(“static”, “static”)
p:value(“dhcp”, “DHCP”)
p.default = “static”
]]--
enable = s:option(Flag, "enable", translate("Enable"))
name = s:option(Value, "username", translate("Username"))
pass = s:option(Value, "password", translate("Password"))
pass.password = true
domain = s:option(Value, "domain", translate("Domain"))
ifname = s:option(ListValue, "ifname", translate("Interfaces"))
for k, v in ipairs(luci.sys.net.devices()) do
    if v ~= "lo" then
        ifname:value(v)
    end
end
local apply = luci.http.formvalue("cbi.apply")
if apply then
    io.popen("/etc/init.d/njitclient restart")
end


return m


控件對應的html文件在luasrc\view\cbi目錄下


5.XHR類
關於XHR的說明,我們可以通過網上的兩篇文章進行一些瞭解:
https://segmentfault.com/a/1190000002782175
https://segmentfault.com/a/1190000002789203
主要是用於實現ajax的http請求,包括post和get.
OpenWrt中已經使用js封裝好了該類的使用.
主要方法有:
//XHR.get(url, data, callback);
XHR.poll(interval, url, data, callback)
(new XHR()).post(url, data, callback);
通過以上方法,可以實現ajax的免刷新獲取數據.


6.利用XHR,完成js與luci的交互.
以修改管理員密碼爲例:
a.在html頁面使用lua編寫對應請求的處理.(往後將探索將lua分開單獨一個文件進行處理)
這部分代碼置於header之前
<%-
    local uci  = require("luci.model.uci").cursor() 
    --retieve data from xhr http request.
    if luci.http.formvalue("status") == "1" then      
        local v1 = luci.http.formvalue("pw1")
local v2 = luci.http.formvalue("pw2")
local applist = ""
local count = ""
if v1 and v2 and #v1 > 0 and #v2 > 0 then
if v1 == v2 then
if luci.sys.user.setpasswd(luci.dispatcher.context.authuser, v1) == 0 then
applist = "suc"
count = 1
else
applist = "fail"
count = 0
end
else
applist = "fail"
count = -1
end
end

        rv = {
            list=applist,
            appnum=count
        }


-- return with json type
        luci.http.prepare_content("application/json")
        luci.http.write_json(rv)


        return
    end
%>


b.使用xhr類,發起修改密碼的post請求
<script type="text/javascript">
var url = '<%=REQUEST_URI%>';
console.log("*****************"+url);
new XHR().post('<%=REQUEST_URI%>', { status: 1 ,pw1:"123",pw2:"123"},
function(xhr) {
            console.log(xhr.responseText);//you will see the xhr.responseText is a json string.
console.log("xhr:"+xhr);
        });
/*************************
使用XHR.poll方法,interval爲刷新頻率(單位s).
********************************
XHR.poll(25,'<%=REQUEST_URI%>', { status: 1 ,pw1:"123",pw2:"123"},
function(x,json){
console.log("x-->"+x.responseText);
console.log("json-->"+json.list);
}
);
************/
</script>


測試結果返回的JSON字符串爲: { "appnum": 1, "list": "suc" }


c.自定義界面的基本思路:
通過查看openwrt源碼,查找到對應功能所需要用到的lua函數,從而通過在html界面調用對應的lua函數實現對應功能的
操作,以達到自定義界面的目的.此方法適用於擅長HTML以及js而剛剛入門開發openwort的開發人員.


參考文章:
http://blog.csdn.net/luanjinlu/article/details/46429241
http://blog.csdn.net/zbffff/article/details/40868463
http://blog.csdn.net/ccwwff/article/details/40790675
http://blog.csdn.net/qq_21949217/article/details/42192627
http://www.jianshu.com/p/bfb93c4e8dc9




7.OpenWrt的UCI系統.
UCI是Openwrt的統一配置接口,通過\etc\config中的配置文件內容的修改,對路由器配置進行修改,如路由器的網絡接口設置,
無線參數設置,logging設置等等.許多第三方程序是根據它自己對應於/etc/config下的UCI配置文件的選項去設置程序的原
始配置文件,這樣就實現了程序對UCI配置的兼容,然後執行一次/etc/init.d腳本完成一次配置。因而當你啓動一個某個程
序的UCI兼容的進程腳本時,該腳本應該不只是修改/etc/config下對應的UCI配置文件,同時也應該覆蓋程序自己的原配置文
件。比如Samba/CIFS程序,其原配置文件是在/etc/samba/smb.conf,而對應的UCI文件是/etc/config/samba,當/etc/config
/samba文件被修改了之後,需要運行一次之後UCI文件中的設置纔會更新到原配置文件中去。
UCI的配置文件被分割成/etc/config下的多個獨立的文件,各個文件按名字含義對應系統的不同的功能配置。你可以通過文本
編譯器或者uci實用程序去修改這些配置文件,同時uci還提供了C語言/腳本/Lua等語言的應用程序接口,WEB配置頁面例如Luci
就是利用了uci所提供的lua的API而實現對UCI配置文件的修改的。


那麼如何通過UCI系統進行配置的修改呢?
首先我們需要了解UCI系統配置文件的編寫規則,這裏不做過多介紹,我們可以參考一下文章
http://developer.t-firefly.com/thread-1035-1-1.html
接下來我們來了解一下關於UCI系統的指令,一般使用shell或者是lua;
a.shell指令集,筆者使用的是lua,因此關於shell的說明請讀者自行參考剛剛提到的UIC系統配置文件編寫規則的文章
http://developer.t-firefly.com/thread-1035-1-1.html


b.lua指令集,Openwrt很方便的給我們提供了一套lua操作UCI的API,封裝於文件lua.model.uci當中,
--因此首先我們應該獲取OpenWrt封裝的操作UCI的API句柄,去定義爲
local x = luci.model.uci.cursor()
--接下來我們介紹使用LUA的api進行配置文件內容的添,刪,查,改


--添加/修改:
x:set("config","name","type") --增加一個section(修改不需要使用此函數)
x:set("config","sectionname","option","exp") --在section下增加配置
參數說明
config -- 配置文件的名字,配置文件位於/etc/config/下
name -- 配置文件中某個類型的具體名字
name/sectionname --具體某一個section的名稱這裏需要注意一點,部分section
--的名稱可能不會直接體現到配置文件當中,因此需要使用先查詢後操作的
--方式(後面會提到查詢的API),一般使用type進行查詢.
type -- 配置文件中類型(type)
option -- 具體配置的option的內容
exp --配置文件中具體option的值


--刪除:
x:delete("config","section") --刪除section
x:delete("config,"section","option") -- 在section下刪除option


--位置插入函數
x:reorder("config","sectionname",position)
將某個section放到postion位置(配置的section是從0開始計數


--查詢:
x:foreach("config","type","function(s) ... end") -- 遍歷整個config文件
x:get("config","sectionname","option") ---獲得option的值
在foreach中有個兩個變量
s[".type"] -->section 類型
s[".name"] -->section名稱,此處爲上文提到的,根據類型查詢名稱的時候需要獲取
--的section名稱sectionName
其中s[".name"] 就是x:get的第二個參數
例:有如下一個配置文件
config globals '0'
     option hostname 'iphone'
     option ip '192.168.0.1'
     option mac '00:11:22:33:44:55:66'
config globals '1'
     option hostname 'iphone1'
     option ip '192.168.0.2'
     option mac '00:11:22:33:44:55:77'
遍歷並且打印每一個option
x:foreach("wificonfig","globals",function(s)
     local lcName = s[".name"]
     local lcHostname = x:get("wificonfig",lcName,"hostname")
     local lcIp = x:get("wificonfig",lcName,"ip")
     local lcMac = x:get("wificonfig",lcName,"mac")
     print("hostname = " .. lcHostname .. ",ip = " .. lcIp .. ",mac= " .. lcMac)
end
)




--[[
需要注意的是:每一次修改完配置後,需要調用x:commit("config")函數,纔會修改到配置文件
另外,要使得配置在系統中生效,需要重啓一次機器
以下是以修改wifi的ssid爲例的具體例子:
]]--
--html文件下添加lua腳本
<%-
    local uci  = require("luci.model.uci").cursor()  
if luci.http.formvalue("status") == "2" then 
local ssidModify=  luci.http.formvalue("ssid")
local ssid = ""
local sectionName = ""
local allMesg = ""
uci:foreach("wireless","wifi-iface",function(s)
allMesg=s
sectionName = s[".name"]
end)
uci:set("wireless",sectionName,"ssid",ssidModify)
uci:commit("wireless")
ssid = uci:get("wireless",sectionName,"ssid")
rv = {
            listSsid=ssid,
listAll=allMesg,
listName=sectionName
        }


        luci.http.prepare_content("application/json")
        luci.http.write_json(rv)
    end

%>
--使用XHR的poll或者是post函數發起請求
XHR.poll(25,'<%=REQUEST_URI%>', { status: 2,ssid:"MyWifiTest"},
function(x,info){
console.log("x-->"+x);
console.log("x-->"+x.responseText);
console.log("info-->"+info.list);
}
);


--[[
以上便是使用LUA的API對openwrt的UCI配置文件進行操作,以達到修改路由器配置
的目的的具體操作步驟.
參考文章:http://blog.csdn.net/dai_xiangjun/article/details/40322539
]]--




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章