Initial checkin of Doodle3D WiFi-box project. Files in src directory belong in /usr/share/lua/autowifi on the device and needs several symlinks as described in the readme.

This commit is contained in:
Wouter R 2013-04-04 10:18:08 +02:00
commit b67d67eb91
20 changed files with 1093 additions and 0 deletions

4
.buildpath Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<buildpath>
<buildpathentry kind="src" path="src"/>
</buildpath>

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>d3d-box-wifi</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.dltk.core.scriptbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.koneki.ldt.nature</nature>
</natures>
</projectDescription>

8
src/README Normal file
View File

@ -0,0 +1,8 @@
Installation:
- place the autowifi directory in /usr/share/lua
- create a symlink to ext/wfcf in /www/cgi-bin (make sure the file is executable)
- create a symlink to ext/autowifi.html in /www
- create a symlink to ext/autowifi.css in /www
- create a symlink to ext/autowifi.js in /www
- create a symlink to ext/autowifi_init in /etc/rc.d and name it S19autowifi_init
- enable init script by calling it with 'enable' argument?

42
src/admin/autowifi.css Normal file
View File

@ -0,0 +1,42 @@
body {
width: 80%;
background-color: #ffe;
}
.catgroup {
border: 1px solid black;
padding: 0.5em;
margin-bottom: 10px;
background-color: white;
-moz-border-radius: 5px;
border-radius: 5px;
}
.cattitle {
display: block;
margin-left: 3em;
margin-bottom: 10px;
font-weight: bold;
}
table.known_nets {
width: 100%;
}
table.known_nets th, table.known_nets td {
width: 33%;
}
.result_success {
font-weight: bold;
color: black;
}
.result_error {
font-weight: normal;
color: red;
}
.hidden {
display: none;
}

37
src/admin/autowifi.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15" />
<title>Doodle3D</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="autowifi.js"></script>
<link href="autowifi.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="catgroup hidden">
<div id="op_result"></div>
</div>
<div class="catgroup">
<span class="cattitle">WiFi mode</span>
<div id="wlan_state"></div>
</div>
<div class="catgroup">
<span class="cattitle">Available networks</span>
<form>
<select id="wlan_networks">
<option value="" disabled selected>Scanning for networks...</option>
</select>
<input id="wlan_passphrase" placeholder="passphrase" value="" type="password" />
<input id="wlan_btn_connect" disabled type="button" value="Connect" />
</form>
</div>
<div class="catgroup">
<span class="cattitle">Known networks</span>
<div id="wlan_known_container"></div>
</div>
</body>
</html>

149
src/admin/autowifi.js Normal file
View File

@ -0,0 +1,149 @@
/*
* TODO
* - finish network creation
* - finish auto operation
* - call auto op from init script
* - in AP mode, route all addresses to the autowifi page
* - escape text where necessary (e.g. in getKnown, '<unknown SSID>' is currently interpreted as html...)
* - why is $.trim() required in string comparison? do we need to strip newlines in the parse functions?
* - add hidden field to remember encryption (so we know whether or not a passphrase should be entered)
* - instead of showing alerts on missing ssid/phrase in connect, disable the button until contraints have been satisfied
* (this is also cleaner in case no networks are present)
* - use json for communication
* - local all functions (see: http://stackoverflow.com/questions/4643814/why-would-this-lua-optimization-hack-help)
*/
animSpeed = 200;
cgiBase = "../cgi-bin/wfcf";
function setResultNeutral(text) {
c = $("#op_result"); p = c.parent();
c.removeClass("result_success").removeClass("result_error").html(text);
if (text == "") p.hide(animSpeed);
else p.show(animSpeed);
}
/*
* Sets div#op_result content to text, assigns appropiate class based on isError and display: block or, with empty text, display:none.
*/
function setResult(text, isError) {
container = $("#op_result");
parent = container.parent();
if (isError) container.removeClass("result_success").addClass("result_error");
else container.removeClass("result_error").addClass("result_success");
if (isError) title = "<i>Error</i><br />\n";
else title = "<i>Success</i><br />\n";
container.html(title + text);
if (text == "") parent.hide(animSpeed);
else parent.show(animSpeed);
}
//Returns an array with key 'status' (OK/WARN/ERR), 'msg' (can be empty) and 'status' (remainder of data)
function parseResponse(response) {
var r = {};
var lines = response.split("\n");
var st = lines[0].trim().split(',');
lines = lines.slice(1);
r['status'] = st[0];
r['msg'] = st.slice(1).join(",");
r['payload'] = lines.join("\n");
return r;
}
function parseNetLine(line) {
var r = {};
line = line.trim().split(",");
r.ssid = line[0];
r.bssid = line[1];
r.channel = line[2];
r.mode = line[3];
return r;
}
function fetchNetworkState() {
$.get(cgiBase + "?op=getstate", function(data) {
data = parseResponse(data);
if (data.status == "ERR") setResult(data.msg, true);
var net = parseNetLine(data.payload);
if (net.mode == "ap") {
$("#wlan_state").text("Access point mode (SSID: " + net.ssid + "; BSSID: " + net.bssid + "; channel: " + net.channel + ")");
} else {
$("#wlan_state").text("Client mode (SSID: " + net.ssid + "; BSSID: " + net.bssid + "; channel: " + net.channel + ")");
}
});
}
function fetchAvailableNetworks() {
$.get(cgiBase + "?op=getavl", function(data) {
data = parseResponse(data);
if (data.status == "ERR") setResult(data.msg, true);
// else setResult(data.msg, false);
data = data.payload.split("\n");
var options = $("#wlan_networks");
options.empty();
$.each(data, function(index,value) {
if (value != "") {
var ssid = parseNetLine(value).ssid;
options.append($("<option />").val(ssid).text(ssid));
}
});
$("#wlan_btn_connect").prop('disabled', false);
});
}
function fetchKnownNetworks() {
$.get(cgiBase + "?op=getknown", function(data) {
data = parseResponse(data);
if (data.status == "ERR") setResult(data.msg, true);
data = data.payload.split("\n");
var container = $("#wlan_known_container");
container.empty();
container.append("<table class=\"known_nets\"><tr><th>SSID</th><th>BSSID</th><th>channel</th></tr>");
$.each(data, function(index,value) {
if (value != "") {
net = parseNetLine(value);
console.log(net);
container.append("<tr><td>" + net.ssid + "</td><td>" + net.bssid + "</td><td>" + net.channel + "</td></tr>");
}
});
container.append("</table>");
});
}
function connectBtnHandler() {
setResultNeutral("Associating with network...");
ssid = $("#wlan_networks").find(":selected").text();
phrase = $("#wlan_passphrase").val();
if (ssid == "") {
alert("Please select a network");
return;
}
$.get(cgiBase + "?op=assoc&ssid=" + ssid + "&passphrase=" + phrase, function(data) {
data = parseResponse(data);
if (data.status == "ERR") {
setResult(data.msg, true);
} else {
if (data.msg != "") setResult(data.msg, false);
else setResult("Associated! (or are we?)", false);
}
fetchKnownNetworks();
});
return;
}
$(document).ready(function() {
fetchNetworkState();
fetchAvailableNetworks();
fetchKnownNetworks();
$("#wlan_btn_connect").click(connectBtnHandler);
});

7
src/admin/index.html Normal file
View File

@ -0,0 +1,7 @@
<head>
<title>temp admin index</title>
</head>
<html><body>
<a href="updater.html">Updates</a><br />
<a href="autowifi.html">Wireless network management</a><br />
</body></html>

156
src/autowifi.lua Normal file
View File

@ -0,0 +1,156 @@
--[[
Response format:
["OK" | "WARN" | "ERR"]<,{message}>
{comma-separated line 1}
...
{comma-separated line n}
- see autowifi.js for TODO
- general info on wireless config: http://wiki.openwrt.org/doc/uci/wireless
- uci docs: http://wiki.openwrt.org/doc/techref/uci
- parse/generate urls: https://github.com/keplerproject/cgilua/blob/master/src/cgilua/urlcode.lua
- utility functions: http://luci.subsignal.org/trac/browser/luci/trunk/libs/sys/luasrc/sys.lua
- iwinfo tool source: http://luci.subsignal.org/trac/browser/luci/trunk/contrib/package/iwinfo/src/iwinfo.lua?rev=7919
- captive portal -> redirect all web traffic to one page for auth (or network selection)
http://wiki.openwrt.org/doc/howto/wireless.hotspot
]]
--print ("HTTP/1.0 200 OK")
print ("Content-type: text/plain\r\n")
local util = require("util")
local wifi = require("wifihelper")
local uci = require("uci").cursor()
local urlcode = require("urlcode")
local iwinfo = require("iwinfo")
local argOperation, argDevice, argSsid, argPhrase, argRecreate
local errortext = nil
function init()
local qs = os.getenv("QUERY_STRING")
local urlargs = {}
urlcode.parsequery(qs, urlargs)
--supplement urlargs with arguments from the command-line
for _, v in ipairs(arg) do
local split = v:find("=")
if split ~= nil then
urlargs[v:sub(1, split - 1)] = v:sub(split + 1)
end
end
argOperation = urlargs["op"]
argDevice = urlargs["dev"] or DFL_DEVICE
argSsid = urlargs["ssid"]
argPhrase = urlargs["phrase"]
argRecreate = urlargs["recreate"]
if urlargs["echo"] ~= nil then
print("[[echo: '"..qs.."']]");
end
if argOperation == nil then
errortext = "Missing operation specifier"
return false
end
return wifi.init()
end
function main()
if argOperation == "getavl" then
local sr = wifi.getScanInfo()
local si, se
if sr and #sr > 0 then
for _, se in ipairs(sr) do
--print("[[ " .. util.dump(se) .. " ]]") --TEMP
util.printWithSuccess(#sr .. " network(s) found");
print(se.ssid .. "," .. se.bssid .. "," .. se.channel .. "," .. wifi.mapDeviceMode(se.mode))
end
else
util.exitWithError("No scan results or scanning not possible")
end
elseif argOperation == "getknown" then
for _, net in ipairs(wifi.getConfigs()) do
if net.mode == "sta" then
local bssid = net.bssid or "<unknown BSSID>"
local channel = net.channel or "<unknown channel>"
util.printWithSuccess("")
print(net.ssid .. "," .. bssid .. "," .. channel)
end
end
elseif argOperation == "getstate" then
local ds = wifi.getDeviceState()
local ssid = ds.ssid or "<unknown SSID>"
local bssid = ds.bssid or "<unknown BSSID>"
local channel = ds.channel or "<unknown channel>"
util.printWithSuccess("");
print(ssid .. "," .. bssid .. "," .. channel .. "," .. ds.mode)
elseif argOperation == "assoc" then
if argSsid == nil or argSsid == "" then util.exitWithError("Please supply an SSID to associate with") end
local cfg = nil
for _, net in ipairs(wifi.getConfigs()) do
if net.mode ~= "ap" and net.ssid == argSsid then
cfg = net
break
end
end
if cfg == nil or argRecreate ~= nil then
scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult)
else
--check for error
util.exitWithError("No wireless network with SSID '" .. argSsid .. "' is available")
end
end
wifi.activateConfig(argSsid)
--restartWlan()
util.printWithSuccess("");
print("Wlan associated with network "..argSsid.."! (dummy mode, not restarting)")
elseif argOperation == "disassoc" then
wifi.activateConfig()
--restartWlan()
exitWithSuccess("Deactivated all wireless networks (dummy mode, not restarting)")
elseif argOperation == "rm" then
if argSsid == nil or argSsid == "" then util.exitWithError("Please supply an SSID to remove") end
if wifi.removeConfig(argSsid) then
exitWithSuccess("Removed wireless network with SSID " .. argSsid)
else
exitWithWarning("No wireless network with SSID " .. argSsid)
end
elseif argOperation == "auto" then
exitWithWarning("Not implemented");
--scan nets
--take union of scan and known
--connect to first if not empty; setup ap otherwise
else
util.exitWithError("Unknown operation: '" .. argOperation .. "'")
end
os.exit(0)
end
--[[ START OF CODE ]]--
if init() == false then
util.exitWithError(errortext)
end
if wifi.createOrReplaceApConfig() == false then
util.exitWithError(errortext)
end
main()

148
src/ext/autowifi.js Normal file
View File

@ -0,0 +1,148 @@
/*
* TODO
* - finish network creation
* - finish auto operation
* - call auto op from init script
* - in AP mode, route all addresses to the autowifi page
* - escape text where necessary (e.g. in getKnown, '<unknown SSID>' is currently interpreted as html...)
* - why is $.trim() required in string comparison? do we need to strip newlines in the parse functions?
* - add hidden field to remember encryption (so we know whether or not a passphrase should be entered)
* - instead of showing alerts on missing ssid/phrase in connect, disable the button until contraints have been satisfied
* (this is also cleaner in case no networks are present)
* - use json for communication
*/
animSpeed = 200;
cgiPath = "asdasd/cgi-bin/wfcf";
function setResultNeutral(text) {
c = $("#op_result"); p = c.parent();
c.removeClass("result_success").removeClass("result_error").html(text);
if (text == "") p.hide(animSpeed);
else p.show(animSpeed);
}
/*
* Sets div#op_result content to text, assigns appropiate class based on isError and display: block or, with empty text, display:none.
*/
function setResult(text, isError) {
container = $("#op_result");
parent = container.parent();
if (isError) container.removeClass("result_success").addClass("result_error");
else container.removeClass("result_error").addClass("result_success");
if (isError) title = "<i>Error</i><br />\n";
else title = "<i>Success</i><br />\n";
container.html(title + text);
if (text == "") parent.hide(animSpeed);
else parent.show(animSpeed);
}
//Returns an array with key 'status' (OK/WARN/ERR), 'msg' (can be empty) and 'status' (remainder of data)
function parseResponse(response) {
var r = {};
var lines = response.split("\n");
var st = lines[0].trim().split(',');
lines = lines.slice(1);
r['status'] = st[0];
r['msg'] = st.slice(1).join(",");
r['payload'] = lines.join("\n");
return r;
}
function parseNetLine(line) {
var r = {};
line = line.trim().split(",");
r.ssid = line[0];
r.bssid = line[1];
r.channel = line[2];
r.mode = line[3];
return r;
}
function fetchNetworkState() {
$.get(cgiBase + "?op=getstate", function(data) {
data = parseResponse(data);
if (data.status == "ERR") setResult(data.msg, true);
var net = parseNetLine(data.payload);
if (net.mode == "ap") {
$("#wlan_state").text("Access point mode (SSID: " + net.ssid + "; BSSID: " + net.bssid + "; channel: " + net.channel + ")");
} else {
$("#wlan_state").text("Client mode (SSID: " + net.ssid + "; BSSID: " + net.bssid + "; channel: " + net.channel + ")");
}
});
}
function fetchAvailableNetworks() {
$.get(cgiPath + "?op=getavl", function(data) {
data = parseResponse(data);
if (data.status == "ERR") setResult(data.msg, true);
// else setResult(data.msg, false);
data = data.payload.split("\n");
var options = $("#wlan_networks");
options.empty();
$.each(data, function(index,value) {
if (value != "") {
var ssid = parseNetLine(value).ssid;
options.append($("<option />").val(ssid).text(ssid));
}
});
$("#wlan_btn_connect").prop('disabled', false);
});
}
function fetchKnownNetworks() {
$.get(cgiPath + "?op=getknown", function(data) {
data = parseResponse(data);
if (data.status == "ERR") setResult(data.msg, true);
data = data.payload.split("\n");
var container = $("#wlan_known_container");
container.empty();
container.append("<table class=\"known_nets\"><tr><th>SSID</th><th>BSSID</th><th>channel</th></tr>");
$.each(data, function(index,value) {
if (value != "") {
net = parseNetLine(value);
console.log(net);
container.append("<tr><td>" + net.ssid + "</td><td>" + net.bssid + "</td><td>" + net.channel + "</td></tr>");
}
});
container.append("</table>");
});
}
function connectBtnHandler() {
setResultNeutral("Associating with network...");
ssid = $("#wlan_networks").find(":selected").text();
phrase = $("#wlan_passphrase").val();
if (ssid == "") {
alert("Please select a network");
return;
}
$.get(cgiPath + "?op=assoc&ssid=" + ssid + "&passphrase=" + phrase, function(data) {
data = parseResponse(data);
if (data.status == "ERR") {
setResult(data.msg, true);
} else {
if (data.msg != "") setResult(data.msg, false);
else setResult("Associated! (or are we?)", false);
}
fetchKnownNetworks();
});
return;
}
$(document).ready(function() {
fetchNetworkState();
fetchAvailableNetworks();
fetchKnownNetworks();
$("#wlan_btn_connect").click(connectBtnHandler);
});

12
src/ext/autowifi_init Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh /etc/rc.common
START=19
start() {
echo "dummy auto wifi init"
#/www/cgi-bin/wfcf op=auto
}
stop() {
echo "dummy auto wifi deinit"
}

10
src/ext/wfcf Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
#chmod 755 /www/cgi-bin/wfcf
LUA=lua
SCRIPT_PATH=/usr/share/lua/autowifi
cd $SCRIPT_PATH
$LUA ./autowifi.lua $@
#$LUA $SCRIPT_PATH/autowifi.lua $@

12
src/misc/old/post Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
echo "Content-type: text/plain"
echo ""
read QUERY_STRING
eval $(echo "$QUERY_STRING"|awk -F'&' '{for(i=1;i<=NF;i++){print $i}}')
#escaped
#echo $txtOutput
#unescaped
txtOutput=`uhttpd -d $txtOutput`
echo $txtOutput

9
src/misc/old/test Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
echo "Content-type: text/plain"
echo ""
echo "Sample CGI Output"
echo ""
echo ""
env
echo ""
echo ""

View File

@ -0,0 +1,63 @@
<html>
<head>
<title>Doodle3D</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$.get("cgi-bin/wifi-scan", function(data) {
data = data.split("\n");
console.log(data.length);
var options = $("#boxNetwork");
options.empty();
$.each(data, function(index,value) {
console.log(value);
if (value!="") options.append($("<option />").val(value).text(value));
});
});
});
</script>
<style type="text/css">
body {
width: 80%;
background-color: #ffe;
}
.catgroup {
border: 1px solid black;
padding: 0.5em;
margin-bottom: 10px;
background-color: white;
-moz-border-radius: 5px;
border-radius: 5px;
}
.cattitle {
display: block;
margin-left: 3em;
margin-bottom: 10px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="catgroup">
<span class="cattitle">WiFi mode</span>
</div>
<div class="catgroup">
<span class="cattitle">Available networks</span>
<select id="boxNetwork">
<option value="" disabled selected>Scanning for neworks...</option>
</select>
<input id="txtPassword" placeholder="password" value="" type="password">
<input id="btnConnect" type="button" value="Connect">
</div>
<div class="catgroup">
<span class="cattitle">Known networks</span>
</div>
</body>
</html>

19
src/misc/old/wifi-scan Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
# avoiding "Device or resource busy" error as per https://forum.openwrt.org/viewtopic.php?pid=121485#p121485
# note that iwinfo takes care of this internally
echo "Content-type: text/plain"
echo ""
DEV="${1:-wlan0}"
ifconfig $DEV down
iw dev $DEV interface add scan.$DEV type station
ifconfig scan.$DEV up
iwlist scan.$DEV scan | grep ESSID | cut -c 27- | tr -d '"'
ifconfig scan.$DEV down
iw dev scan.$DEV del
ifconfig $DEV up

33
src/misc/temp Normal file
View File

@ -0,0 +1,33 @@
[[1: { ["encryption"] =
{ ["enabled"] = true,
["auth_algs"] = { } ,
["description"] = mixed WPA/WPA2 PSK (TKIP),
["wep"] = false,
["auth_suites"] =
{ [1] = PSK,} ,
["wpa"] = 3,
["pair_ciphers"] =
{ [1] = TKIP,
[2] = CCMP,
} ,
["group_ciphers"] =
{ [1] = TKIP,} ,
} ,
["quality_max"] = 70,
["ssid"] = happiesnappie2,
["channel"] = 9,
["signal"] = -35,
["bssid"] = 00:22:6B:EF:BB:99,
["mode"] = Master,
["quality"] = 70,
} ]]
config wifi-iface
option network 'lan'
option ssid 'happiesnappie2'
option encryption 'psk2'
option device 'radio0'
option mode 'sta'
option bssid '00:22:6B:EF:BB:99'
option key 'pr4ppal4trappa'
option disabled '1'

45
src/misc/wireless.org Normal file
View File

@ -0,0 +1,45 @@
config wifi-device 'radio0'
option type 'mac80211'
option hwmode '11ng'
option path 'platform/ar933x_wmac'
option htmode 'HT20'
list ht_capab 'SHORT-GI-20'
list ht_capab 'SHORT-GI-40'
list ht_capab 'RX-STBC1'
list ht_capab 'DSSS_CCK-40'
option channel '9'
option country 'NL'
option txpower '20'
option disabled '0'
config wifi-iface
option network 'lan'
option ssid 'happiesnappie2'
option encryption 'psk2'
option device 'radio0'
option mode 'sta'
option bssid '00:22:6B:EF:BB:99'
option key 'pr4ppal4trappa'
option disabled '1'
config wifi-iface
option device 'radio0'
option encryption 'none'
option ssid 'non-existing'
option mode 'sta'
option network 'lan'
option disabled '0'
config wifi-iface
option encryption 'none'
option device 'radio0'
option mode 'ap'
option ssid 'd3d-ap'
option network 'lan'
option disabled '1'
Content-type: text/plain
[[1: { ["encryption"] = { ["enabled"] = true,["auth_algs"] = { } ,["description"] = mixed WPA/WPA2 PSK (TKIP),["wep"] = false,["auth_suites"] = { [1] = PSK,} ,["wpa"] = 3,["pair_ciphers"] = { [1] = TKIP,[2] = CCMP,} ,["group_ciphers"] = { [1] = TKIP,} ,} ,["quality_max"] = 70,["ssid"] = happiesnappie2,["channel"] = 9,["signal"] = -35,["bssid"] = 00:22:6B:EF:BB:99,["mode"] = Master,["quality"] = 70,} ]]
happiesnappie2

114
src/urlcode.lua Normal file
View File

@ -0,0 +1,114 @@
----------------------------------------------------------------------------
-- Utility functions for encoding/decoding of URLs.
--
-- @release $Id: urlcode.lua,v 1.10 2008/01/21 16:11:32 carregal Exp $
----------------------------------------------------------------------------
local ipairs, next, pairs, tonumber, type = ipairs, next, pairs, tonumber, type
local string = require"string"
local gsub = string.gsub
local strbyte, strchar, strformat, strsub = string.byte, string.char, string.format, string.sub
local tinsert = require"table".insert
--module ("cgilua.urlcode")
local _M = {}
-- Converts an hexadecimal code in the form %XX to a character
local function hexcode2char (h)
return strchar(tonumber(h,16))
end
----------------------------------------------------------------------------
-- Decode an URL-encoded string (see RFC 2396)
----------------------------------------------------------------------------
function _M.unescape (str)
str = gsub (str, "+", " ")
str = gsub (str, "%%(%x%x)", hexcode2char)
str = gsub (str, "\r\n", "\n")
return str
end
-- Converts a character to an hexadecimal code in the form %XX
local function char2hexcode (c)
return strformat ("%%%02X", strbyte(c))
end
----------------------------------------------------------------------------
-- URL-encode a string (see RFC 2396)
----------------------------------------------------------------------------
function _M.escape (str)
str = gsub (str, "\n", "\r\n")
str = gsub (str, "([^0-9a-zA-Z ])", char2hexcode) -- locale independent
str = gsub (str, " ", "+")
return str
end
----------------------------------------------------------------------------
-- Insert a (name=value) pair into table [[args]]
-- @param args Table to receive the result.
-- @param name Key for the table.
-- @param value Value for the key.
-- Multi-valued names will be represented as tables with numerical indexes
-- (in the order they came).
----------------------------------------------------------------------------
function _M.insertfield (args, name, value)
if not args[name] then
args[name] = value
else
local t = type (args[name])
if t == "string" then
args[name] = {
args[name],
value,
}
elseif t == "table" then
tinsert (args[name], value)
else
error ("CGILua fatal error (invalid args table)!")
end
end
end
----------------------------------------------------------------------------
-- Parse url-encoded request data
-- (the query part of the script URL or url-encoded post data)
--
-- Each decoded (name=value) pair is inserted into table [[args]]
-- @param query String to be parsed.
-- @param args Table where to store the pairs.
----------------------------------------------------------------------------
function _M.parsequery (query, args)
if type(query) == "string" then
local insertfield, unescape = _M.insertfield, _M.unescape
gsub (query, "([^&=]+)=([^&=]*)&?",
function (key, val)
_M.insertfield (args, unescape(key), unescape(val))
end)
end
end
----------------------------------------------------------------------------
-- URL-encode the elements of a table creating a string to be used in a
-- URL for passing data/parameters to another script
-- @param args Table where to extract the pairs (name=value).
-- @return String with the resulting encoding.
----------------------------------------------------------------------------
function _M.encodetable (args)
if args == nil or next(args) == nil then -- no args or empty args?
return ""
end
local escape = _M.escape
local strp = ""
for key, vals in pairs(args) do
if type(vals) ~= "table" then
vals = {vals}
end
for i,val in ipairs(vals) do
strp = strp.."&"..escape(key).."="..escape(val)
end
end
-- remove first &
return strsub(strp,2)
end
return _M

36
src/util.lua Normal file
View File

@ -0,0 +1,36 @@
local M = {}
function M.dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. M.dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
function M.printWithSuccess(msg)
if msg ~= nil and msg ~= "" then print("OK," .. msg)
else print("OK") end
end
function M.exitWithSuccess(msg)
if msg ~= nil and msg ~= "" then print("OK," .. msg)
else print("OK") end
os.exit(0)
end
function M.exitWithWarning(msg)
if msg ~= nil and msg ~= "" then print("WARN," .. msg)
else print("OK") end
os.exit(0)
end
function M.exitWithError(msg)
if msg ~= nil and msg ~= "" then print("ERR," .. msg)
else print("OK") end
os.exit(1)
end
return M

172
src/wifihelper.lua Normal file
View File

@ -0,0 +1,172 @@
local util = require("util")
local uci = require("uci").cursor()
local iwinfo = require("iwinfo")
local M = {}
M.DFL_AP_SSID = "d3d-ap"
M.DFL_DEVICE = "radio0" -- was wlan0
local dev, dev_api
--- Map device mode as reported by iwinfo to device mode as required by UCI
-- Note that this function is quite naive.
-- @param mode mode text as reported by iwinfo
-- @param masterIsAp set to true to map 'Master' to 'ap' instead of 'sta' (optional)
function M.mapDeviceMode(mode, masterIsAp)
local modeMap = {
["Master"] = masterIsAp and "ap" or "sta",
["Ad-Hoc"] = "adhoc"
}
return modeMap[mode]
end
--- Initialize WiFi helper library
-- @param device wireless device to operate on (optional, defaults to DFL_DEVICE)
-- @return true on success or false+error on failure
function M.init(device)
-- iwinfo = pcall(require, "iwinfo")
dev = device or M.DFL_DEVICE
dev_api = iwinfo.type(dev)
if not dev_api then
return false, "No such wireless device: '"..dev.."'"
end
return true
end
function M.getDeviceState()
local iw = iwinfo[dev_api]
local result = {
["mode"] = M.mapDeviceMode(iw.mode(dev), true),
["ssid"] = iw.ssid(dev),
["bssid"] = iw.bssid(dev)
}
return result
end
--- Return one or all available wifi networks resulting from an iwinfo scan
-- @param ssid return data for given SSID or for all networks if SSID not given
-- @return data for all or requested network; false+error on failure or nil when requested network not found
function M.getScanInfo(ssid)
local iw = iwinfo[dev_api]
local sr = iw.scanlist(dev)
local si, se
if ssid == nil then
return sr
else
if sr and #sr > 0 then
for _, se in ipairs(sr) do
if se.ssid == ssid then
return se
end
end
else
return false, "No scan results or scanning not possible"
end
end
return nil
end
--- Return all wireless networks configured in UCI
function M.getConfigs()
local l = {}
uci.foreach("wireless", "wifi-iface", function(s) table.insert(l, s) end)
return l
end
--- Remove UCI config for network with given SSID
-- @return true if successfully removed, false if no such config exists
function M.removeConfig(ssid)
local rv = false
uci:foreach("wireless", "wifi-iface", function(s)
if s.ssid == ssid then
uci:delete("wireless", s[".name"])
rv = true
return false
end
end)
uci:commit("wireless")
return rv
end
--- Activate wireless section for given SSID and disable all others
-- @param ssid SSID of config to enable, or nil to disable all network configs
function M.activateConfig(ssid)
uci:foreach("wireless", "wifi-iface", function(s)
local disabled = s.ssid ~= ssid and "1" or "0"
uci:set("wireless", s[".name"], "disabled", disabled)
end)
uci:commit("wireless")
end
--- Create a new UCI network from the given iwinfo data
-- http://luci.subsignal.org/trac/browser/luci/trunk/libs/iwinfo/src/iwinfo_wext.c?rev=5645 (outdated?)
-- TODO: configure encryption correctly (how?)
-- @param info iwinfo data to create a network from
-- @param passphrase passphrase to use (optional)
-- @param disabled immediately disable the network (optional)
function M.createConfigFromScanInfo(info, passphrase, disabled)
local mode = M.mapDeviceMode(info.mode)
local apconfig = {
network = "lan",
device = "radio0",
ssid = info.ssid,
bssid = info.bssid,
--encryption = "none",
mode = mode,
}
if passphrase ~= nil then apconfig.key = passphrase end
apconfig.disabled = disabled ~= nil and disabled and 1 or 0
local sname = uci:add("wireless", "wifi-iface");
for k, v in pairs(apconfig) do
uci:set("wireless", sname, k, v)
end
uci:commit("wireless")
end
--- Ensure a suitable config section for the access point network exists
-- @param disabled flag the network as disabled (optional)
function M.createOrReplaceApConfig(disabled)
local sname = nil
uci:foreach("wireless", "wifi-iface", function(s)
if s.ssid == M.AP_SSID then
sname = s[".name"]
return false
end
end)
if sname == nil then sname = uci:add("wireless", "wifi-iface") end
local apconfig = {
network = "lan",
ssid = M.AP_SSID,
encryption = "none",
device = "radio0",
mode = "ap",
}
apconfig.disabled = disabled ~= nil and disabled and 1 or 0
for k, v in pairs(apconfig) do
uci:set("wireless", sname, k, v)
end
uci:commit("wireless")
end
--- Reload network config to reflect contents of config
-- @see http://wiki.openwrt.org/doc/techref/netifd)
-- * Network reload only restarts interfaces which need to be restarted so no
-- unneccesary interruptions there.
-- * ubus does not seem to work -- local c=ubus.connect();
-- c:call("network.interface.wlan", "down"); c:call("network.interface.wlan", "up"); c:close()
function M.restart()
local rv = os.execute("/etc/init.d/network reload")
end
return M