-
-
-
-]],
-
--- board status section on front page
-["@DYNAMIC/board_status.shtml"] = [[
-
+ ["@DYNAMIC/board_status.shtml"] = [[
+
Firmware
GIT Hash
Uptime
@@ -226,42 +196,17 @@ local DYNAMIC_PAGES = {
]]
}
-reboot_counter = 0
-
-local ACTION_PAGES = {
- ["/?FWUPDATE"] = function()
- periph:can_printf("Rebooting for firmware update")
- reboot_counter = 50
- end
+-- large browser assets stay in ROMFS instead of consuming the Lua heap
+local ROMFS_PAGES = {
+ ["/"] = "@ROMFS/pppgw.html"
}
---[[
- builtin javascript library functions
---]]
-JS_LIBRARY = {
- ["dynamic_load"] = [[
- function dynamic_load(div_id, uri, period_ms) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', uri);
-
- xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
- xhr.setRequestHeader("Expires", "Tue, 01 Jan 1980 1:00:00 GMT");
- xhr.setRequestHeader("Pragma", "no-cache");
-
- xhr.onload = function () {
- if (xhr.status === 200) {
- var output = document.getElementById(div_id);
- if (uri.endsWith('.shtml') || uri.endsWith('.html')) {
- output.innerHTML = xhr.responseText;
- } else {
- output.textContent = xhr.responseText;
- }
- }
- setTimeout(function() { dynamic_load(div_id,uri, period_ms); }, period_ms);
- }
- xhr.send();
- }
-]]
+local reboot_counter = nil
+local reboot_hold_in_bootloader = false
+
+local ACTION_PAGES = {
+ ["/?REBOOT"] = false,
+ ["/?FWUPDATE"] = true
}
if not sock_listen:bind("0.0.0.0", WEB_BIND_PORT:get()) then
@@ -308,6 +253,77 @@ local function startswith(str, s)
return string.sub(str,1,#s) == s
end
+-- decode a query-string component
+local function url_decode(str)
+ str = string.gsub(str, "+", " ")
+ return string.gsub(str, "%%(%x%x)", function(hex)
+ return string.char(tonumber(hex, 16))
+ end)
+end
+
+-- parse the key/value pairs from a HTTP query string
+local function parse_query(query)
+ local vars = {}
+ for pair in string.gmatch(query or "", "[^&]+") do
+ local key, value = string.match(pair, "^([^=]+)=(.*)$")
+ if key then
+ vars[url_decode(key)] = url_decode(value)
+ end
+ end
+ return vars
+end
+
+-- return a request header using a case-insensitive name match
+local function header_value(headers, name)
+ for key, value in pairs(headers) do
+ if string.lower(key) == name then
+ return value
+ end
+ end
+end
+
+-- create a one-parameter packed upload, preserving integer values exactly
+local function parameter_upload(name, parameter_type, value_string)
+ local value = tonumber(value_string)
+ local packed_value
+ local response_value
+
+ if parameter_type == 4 then
+ if not value or value ~= value or value == math.huge or value == -math.huge then
+ return nil
+ end
+ packed_value = string.pack("= -128 and value <= 127 then
+ packed_value = string.pack("= -32768 and value <= 32767 then
+ packed_value = string.pack("= -2147483648 and value <= 2147483647 then
+ packed_value = string.pack(" 16 or not string.match(name, "^[A-Z0-9_]+$") then
+ error_message = "Invalid parameter name"
+ elseif not value_string or not parameter_type or parameter_type < 1 or parameter_type > 4 then
+ error_message = "Invalid parameter value"
+ elseif param:get(name) == nil then
+ error_message = "Parameter not found"
+ else
+ response_value = parameter_upload(name, parameter_type, value_string)
+ end
+ if not error_message and not response_value then
+ error_message = "Parameter save failed"
+ end
+
+ local headers = {
+ ["Content-Type"] = "application/json",
+ ["Cache-Control"] = "no-cache, no-store, max-age=0"
+ }
+ if error_message then
+ self.send_header(400, "Bad Request", headers)
+ self.sendstring(string.format('{"ok":false,"error":"%s"}', error_message))
+ return
+ end
+ self.send_header(200, "OK", headers)
+ self.sendstring(string.format('{"ok":true,"value":%s}', response_value))
+ end
+
-- get size of a file
function self.file_size(fname)
local s = fs:stat(fname)
@@ -617,7 +671,7 @@ local function Client(sock, idx)
if not sock:pollout(0) then
return
end
- local chunk = WEB_BLOCK_SIZE:get()
+ local chunk = file_block_size or WEB_BLOCK_SIZE:get()
local b = file:read(chunk)
sock:set_blocking(true)
if b and #b > 0 then
@@ -752,6 +806,7 @@ local function Client(sock, idx)
path = string.sub(path, 2, #path)
end
DEBUG(string.format("%u: file_download(%s)", idx, path))
+ file_block_size = startswith(path, "@") and 1024 or nil
file = DYNAMIC_PAGES[path]
dynamic_page = file ~= nil
if not dynamic_page then
@@ -849,9 +904,19 @@ local function Client(sock, idx)
end
end
+ local request_path, query = string.match(path, "^([^?]*)[?]?(.*)$")
+ if request_path == "/@DYNAMIC/param_set.shtml" then
+ self.parameter_set(query)
+ return
+ end
+
if ACTION_PAGES[path] ~= nil then
DEBUG(string.format("Running ACTION %s", path))
- local fn = ACTION_PAGES[path]
+ if header_value(header_vars, 'x-ardupilot-webui') ~= 'action' then
+ self.send_header(400, "Bad Request", {["Content-Type"]="text/plain;charset=UTF-8"})
+ self.sendstring("Invalid request")
+ return
+ end
self.send_header(200, "OK", {["Content-Type"]=CONTENT_TEXT_HTML})
self.sendstring([[
@@ -860,7 +925,18 @@ local function Client(sock, idx)