0
0
mirror of https://github.com/Doodle3D/doodle3d-firmware.git synced 2024-12-22 19:13:49 +01:00

Refactor all code to new API; several minor changes/bug fixes.

This commit is contained in:
Wouter R 2013-07-08 16:53:45 +02:00
parent 915d1cd900
commit 115e6a7eff
7 changed files with 236 additions and 263 deletions

View File

@ -1,13 +1,36 @@
--[[
TODO:
- network/state returns awfully little information (only station mode)
- document REST API (mention rq IDs and endpoint information, list endpoints+args+CRUD type, unknown values are empty fields)
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
- how to handle requests which need a restart of uhttpd? (e.g. network/openap)
- a plain GET request (no ajax/script) runs the risk of timing out on lengthy operations: implement polling in API to get progress updates?
(this would require those operations to run in a separate daemon process which can be monitored by the CGI handler)
- protect dump function against reference loops (see: http://lua-users.org/wiki/TableSerialization, json also handles this well)
- (this is an old todo item from network:available(), might still be relevant at some point)
extend reconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
NOTES:
- The endpoint function info in response objects is incorrect when the global function is called with a blank argument,
to cleanly solve this, module/function resolution should be moved from main() to the request object
]]--
local l = require("logger") local l = require("logger")
local RequestClass = require("rest.request") local RequestClass = require("rest.request")
local ResponseClass = require("rest.response") local ResponseClass = require("rest.response")
local wifi = require("network.wlanconfig")
local reconf = require("network.netconfig")
local DEBUG_PCALLS = false --NOTE: pcall protects from invocation exceptions, which is what we need except
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
local DEBUG_PCALLS = true
local postData = nil local postData = nil
local resp = ResponseClass.new()
local function setupAutoWifiMode() local function setupAutoWifiMode()
@ -26,6 +49,8 @@ local function init()
local n = tonumber(os.getenv("CONTENT_LENGTH")) local n = tonumber(os.getenv("CONTENT_LENGTH"))
postData = io.read(n) postData = io.read(n)
end end
return wifi.init() and reconf.init(wifi, true)
end end
--usually returns function+nil, function+number in case of number in place of function name; or --usually returns function+nil, function+number in case of number in place of function name; or
@ -96,12 +121,14 @@ end
if ok == true then if ok == true then
print(r:serializeAsJson()) print(r:serializeAsJson())
else else
local resp = ResponseClass.new(rq)
resp:setError("call to function '" .. mod .. "/" .. sr .. "' failed") resp:setError("call to function '" .. mod .. "/" .. sr .. "' failed")
print(resp:serializeAsJson()) print(resp:serializeAsJson())
l:error("calling function '" .. func .. "' in API module '" .. mod .. "' somehow failed ('" .. r .. "')") l:error("calling function '" .. func .. "' in API module '" .. mod .. "' somehow failed ('" .. r .. "')")
end end
else else
resp:setError("function unknown '" .. mod .. "/" .. func .. "'") local resp = ResponseClass.new(rq)
resp:setError("function unknown '" .. (mod or "<empty>") .. "/" .. (func or "<global>") .. "'")
print(resp:serializeAsJson()) print(resp:serializeAsJson())
l:error("could not resolve requested API function ('" .. sr .. "')") l:error("could not resolve requested API function ('" .. sr .. "')")
end end
@ -109,5 +136,13 @@ end
end end
init() if init() == false then
main() local resp = ResponseClass.new()
resp:setError("initialization failed")
print(resp:serializeAsJson()) --FIXME: this message does not seem to be sent
l:error("initialization failed") --NOTE: this assumes the logger has been inited properly, despite init() having failed
os.exit(1)
else
main()
os.exit(0)
end

View File

@ -1,173 +0,0 @@
--[[
Response format:
["OK" | "WARN" | "ERR"]<,{message}>
{comma-separated line 1}
...
{comma-separated line n}
- 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")
io.write ("Content-type: text/plain\r\n\r\n")
local u = require("util")
local l = require("logger")
local wifi = require("network.wlanconfig")
local reconf = require("network.netconfig")
local urlcode = require("util.urlcode")
local uci = require("uci").cursor()
local iwinfo = require("iwinfo")
local argOperation, argDevice, argSsid, argPhrase, argRecreate
local errortext = nil
function init()
l:init(l.LEVEL.debug, true, io.stderr)
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() and reconf.init(wifi, true)
end
function main()
if argOperation == "getavl" then
local sr = wifi.getScanInfo()
local si, se
--TODO:
-- - extend reconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
-- but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
-- or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
-- in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
if sr and #sr > 0 then
u.printWithSuccess(#sr .. " network(s) found");
for _, se in ipairs(sr) do
--print("[[ " .. u.dump(se) .. " ]]") --TEMP
if se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
print(se.ssid .. "," .. se.bssid .. "," .. se.channel .. "," .. wifi.mapDeviceMode(se.mode) .. "," .. wifi.mapEncryptionType(se.encryption))
end
end
else
u.exitWithError("No scan results or scanning not possible")
end
elseif argOperation == "getknown" then
u.printWithSuccess("")
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>"
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>"
u.printWithSuccess("");
print(ssid .. "," .. bssid .. "," .. channel .. "," .. ds.mode)
elseif argOperation == "assoc" then
if argSsid == nil or argSsid == "" then u.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
local scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, argPhrase)
else
--check for error
u.exitWithError("No wireless network with SSID '" .. argSsid .. "' is available")
end
end
wifi.activateConfig(argSsid)
reconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
u.exitWithSuccess("Wlan associated with network " .. argSsid .. "!")
elseif argOperation == "disassoc" then
wifi.activateConfig()
local rv = wifi.restart()
u.exitWithSuccess("Deactivated all wireless networks [$?=" .. rv .. "]")
elseif argOperation == "openap" then
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
reconf.switchConfiguration{apnet="add_noreload"}
wifi.activateConfig(wifi.AP_SSID)
reconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
u.exitWithSuccess("Switched to AP mode (SSID: '" .. wifi.AP_SSID .. "')")
elseif argOperation == "rm" then
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to remove") end
if wifi.removeConfig(argSsid) then
u.exitWithSuccess("Removed wireless network with SSID " .. argSsid)
else
u.exitWithWarning("No wireless network with SSID " .. argSsid)
end
elseif argOperation == "test" then
--invert actions performed by openap operation
reconf.switchConfiguration{ apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm" }
-- reconf.switchConfiguration{dnsredir="add"}
u.exitWithSuccess("nop")
elseif argOperation == "auto" then
u.exitWithWarning("Not implemented");
--scan nets
--take union of scan and known
--connect to first if not empty; setup ap otherwise
else
u.exitWithError("Unknown operation: '" .. argOperation .. "'")
end
os.exit(0)
end
--[[ START OF CODE ]]--
if init() == false then
u.exitWithError(errortext)
end
main()

View File

@ -1,95 +1,179 @@
local l = require("logger") local l = require("logger")
local u = require("util.utils")
local netconf = require("network.netconfig")
local wifi = require("network.wlanconfig")
local ResponseClass = require("rest.response")
local M = {} local M = {}
M.isApi = true M.isApi = true
function M._global(d) function M._global(d)
return "not implemented..." local r = ResponseClass.new(d)
r:setError("not implemented")
return r
end end
--[[ --accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
if argOperation == "getavl" then function M.available(d)
local sr = wifi.getScanInfo() local r = ResponseClass.new(d)
local si, se local noFilter = u.toboolean(d:get("nofilter"))
local sr = wifi.getScanInfo()
local si, se
--TODO: if sr and #sr > 0 then
-- - extend reconf interface to support function arguments (as tables) so wifihelper functionality can be integrated r:setSuccess("")
-- but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x' local netInfoList = {}
-- or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"} for _, se in ipairs(sr) do
-- in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array) if noFilter or se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
if sr and #sr > 0 then local netInfo = {}
u.printWithSuccess(#sr .. " network(s) found");
for _, se in ipairs(sr) do netInfo["ssid"] = se.ssid
--print("[[ " .. u.dump(se) .. " ]]") --TEMP netInfo["bssid"] = se.bssid
if se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then netInfo["channel"] = se.channel
print(se.ssid .. "," .. se.bssid .. "," .. se.channel .. "," .. wifi.mapDeviceMode(se.mode) .. "," .. wifi.mapEncryptionType(se.encryption)) netInfo["mode"] = wifi.mapDeviceMode(se.mode)
end netInfo["encryption"] = wifi.mapEncryptionType(se.encryption)
netInfo["signal"] = se.signal
netInfo["quality"] = se.quality
netInfo["quality_max"] = se.quality_max
--netInfo["raw"] = l:dump(se) --TEMP for debugging only
table.insert(netInfoList, netInfo)
end end
end
r:addData("count", #netInfoList)
r:addData("networks", netInfoList)
else
r:setError("No scan results or scanning not possible")
end
return r
end
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
function M.known(d)
local r = ResponseClass.new(d)
local noFilter = u.toboolean(d:get("nofilter"))
r:setSuccess()
local netInfoList = {}
for _, net in ipairs(wifi.getConfigs()) do
if noFilter or net.mode == "sta" then
local netInfo = {}
netInfo["ssid"] = net.ssid
netInfo["bssid"] = net.bssid or ""
netInfo["channel"] = net.channel or ""
netInfo["encryption"] = net.encryption
netInfo["raw"] = l:dump(net) --TEMP for debugging only
table.insert(netInfoList, netInfo)
end
end
r:addData("count", #netInfoList)
r:addData("networks", netInfoList)
return r
end
function M.state(d)
local r = ResponseClass.new(d)
local ds = wifi.getDeviceState()
r:setSuccess()
r:addData("ssid", ds.ssid or "")
r:addData("bssid", ds.bssid or "")
r:addData("channel", ds.channel or "")
r:addData("mode", ds.mode)
r:addData("raw", l:dump(ds)) --TEMP for debugging only
return r
end
--UNTESTED
--requires ssid(string), accepts phrase(string), recreate(bool)
function M.assoc(d)
local r = ResponseClass.new(d)
local argSsid = d:get("ssid")
local argPhrase = d:get("phrase")
local argRecreate = d:get("recreate")
if argSsid == nil or argSsid == "" then
r:setError("missing ssid argument")
return r
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
local scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, argPhrase)
else else
u.exitWithError("No scan results or scanning not possible") --check for error
r:setError("no wireless network with requested SSID is available")
r:addData("ssid", argSsid)
end end
end
elseif argOperation == "getknown" then wifi.activateConfig(argSsid)
u.printWithSuccess("") netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
for _, net in ipairs(wifi.getConfigs()) do r:setSuccess("wlan associated")
if net.mode == "sta" then r:addData("ssid", argSsid)
local bssid = net.bssid or "<unknown BSSID>"
local channel = net.channel or "<unknown channel>"
print(net.ssid .. "," .. bssid .. "," .. channel)
end
end
elseif argOperation == "getstate" then return r
local ds = wifi.getDeviceState() end
local ssid = ds.ssid or "<unknown SSID>"
local bssid = ds.bssid or "<unknown BSSID>"
local channel = ds.channel or "<unknown channel>"
u.printWithSuccess("");
print(ssid .. "," .. bssid .. "," .. channel .. "," .. ds.mode)
elseif argOperation == "assoc" then --UNTESTED
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to associate with") end function M.disassoc(d)
local r = ResponseClass.new(d)
local cfg = nil wifi.activateConfig()
for _, net in ipairs(wifi.getConfigs()) do local rv = wifi.restart()
if net.mode ~= "ap" and net.ssid == argSsid then r:setSuccess("all wireless networks deactivated")
cfg = net r:addData("wifi_restart_result", rv)
break
end
end
if cfg == nil or argRecreate ~= nil then
local scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, argPhrase)
else
--check for error
u.exitWithError("No wireless network with SSID '" .. argSsid .. "' is available")
end
end
wifi.activateConfig(argSsid)
reconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
u.exitWithSuccess("Wlan associated with network " .. argSsid .. "!")
elseif argOperation == "disassoc" then return r
wifi.activateConfig() end
local rv = wifi.restart()
u.exitWithSuccess("Deactivated all wireless networks [$?=" .. rv .. "]")
elseif argOperation == "openap" then --UNTESTED
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed function M.openap(d)
reconf.switchConfiguration{apnet="add_noreload"} local r = ResponseClass.new(d)
wifi.activateConfig(wifi.AP_SSID)
reconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
u.exitWithSuccess("Switched to AP mode (SSID: '" .. wifi.AP_SSID .. "')")
elseif argOperation == "rm" then --add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to remove") end netconf.switchConfiguration{apnet="add_noreload"}
if wifi.removeConfig(argSsid) then wifi.activateConfig(wifi.AP_SSID)
u.exitWithSuccess("Removed wireless network with SSID " .. argSsid) netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
else r:setSuccess("switched to Access Point mode")
u.exitWithWarning("No wireless network with SSID " .. argSsid) r:addData("ssid", wifi.AP_SSID)
end
]-- return r
end
--UNTESTED
--requires ssid(string)
function M.rm(d)
local r = ResponseClass.new(d)
local argSsid = d:get("ssid")
if argSsid == nil or argSsid == "" then
r:setError("missing ssid argument")
return r
end
if wifi.removeConfig(argSsid) then
r:setSuccess("removed wireless network with requested SSID")
r:addData("ssid", argSsid)
else
r:setError("no wireless network with requested SSID") --this used to be a warning instead of an error...
r:addData("ssid", argSsid)
end
return r
end
return M return M

View File

@ -6,26 +6,29 @@ local M = {}
M.isApi = true M.isApi = true
function M._global(d) function M._global(d)
local r = ResponseClass.new() local r = ResponseClass.new(d)
local ba = d:getBlankArgument() or "<nil>" local ba = d:getBlankArgument()
r:setSuccess("REST test API - default function called with blank argument: '" .. ba .. "'")
r:setSuccess("REST test API - default function called with blank argument: '" .. (ba or "<nil>") .. "'")
if ba ~= nil then r:addData("blank_argument", ba) end
return r return r
end end
function M.success(d) function M.success(d)
local r = ResponseClass.new() local r = ResponseClass.new(d)
r:setSuccess("yay!") r:setSuccess()
return r return r
end end
function M.error(d) function M.error(d)
local r = ResponseClass.new() local r = ResponseClass.new(d)
r:setError("this error has been generated on purpose") r:setError("this error has been generated on purpose")
return r return r
end end
function M.echo(d) function M.echo(d)
local r = ResponseClass.new() local r = ResponseClass.new(d)
r:setSuccess("request echo") r:setSuccess("request echo")
r:addData("request_data", d) r:addData("request_data", d)
return r return r

View File

@ -59,6 +59,9 @@ function M.new(postData, debug)
self.apiFunction = self.cmdLineArgs["f"] or self.apiFunction self.apiFunction = self.cmdLineArgs["f"] or self.apiFunction
end end
if self.apiModule == "" then self.apiModule = nil end
if self.apiFunction == "" then self.apiFunction = nil end
return self return self
end end

View File

@ -3,17 +3,31 @@ local JSON = (loadfile "util/JSON.lua")()
local M = {} local M = {}
M.__index = M M.__index = M
local REQUEST_ID_ARGUMENT = "rq_id"
local INCLUDE_ENDPOINT_INFO = false
setmetatable(M, { setmetatable(M, {
__call = function(cls, ...) __call = function(cls, ...)
return cls.new(...) return cls.new(...)
end end
}) })
function M.new() --requestObject should always be passed (except on init failure, when it is not yet available)
function M.new(requestObject)
local self = setmetatable({}, M) local self = setmetatable({}, M)
self.body = {status = nil, data = {}} self.body = {status = nil, data = {}}
if requestObject ~= nil then
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
if INCLUDE_ENDPOINT_INFO == true then
self.body["module"] = requestObject:getApiModule()
self.body["function"] = requestObject:getApiFunction() or ""
end
end
return self return self
end end
@ -23,12 +37,12 @@ end
function M:setSuccess(msg) function M:setSuccess(msg)
self.body.status = "success" self.body.status = "success"
self.body.msg = msg if msg ~= "" then self.body.msg = msg end
end end
function M:setError(msg) function M:setError(msg)
self.body.status = "error" self.body.status = "error"
self.body.msg = msg if msg ~= "" then self.body.msg = msg end
end end
--NOTE: with this method, to add nested data, it is necessary to precreate the table and add it with its root key --NOTE: with this method, to add nested data, it is necessary to precreate the table and add it with its root key

View File

@ -2,6 +2,13 @@ local uci = require("uci").cursor()
local M = {} local M = {}
function M.toboolean(s)
if not s then return false end
local b = s:lower()
return (b == "1" or b == "t" or b == "true") and true or false
end
function M.getUciSectionName(config, type) function M.getUciSectionName(config, type)
local sname = nil local sname = nil
uci:foreach(config, type, function(s) sname = s[".name"] end) uci:foreach(config, type, function(s) sname = s[".name"] end)