mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 19:13:49 +01:00
Implement settings interface.
This commit is contained in:
parent
9a8ac55b8f
commit
d3e4812cbf
@ -1,5 +1,8 @@
|
||||
local M = {}
|
||||
|
||||
--NOTE: proposed notation for baseline configuration (containing defaults as well as type and constraint information)
|
||||
--the table name is the configuration key; min, max and regex are all optional; type is one of: {bool, int, float, string}
|
||||
|
||||
--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.
|
||||
M.DEBUG_PCALLS = true
|
||||
@ -7,28 +10,38 @@ M.DEBUG_PCALLS = true
|
||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||
|
||||
M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
||||
M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
||||
M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
||||
|
||||
-- was: M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
||||
M.apSsid = {
|
||||
default = 'd3d-ap-%%MAC_ADDR_TAIL%%',
|
||||
type = 'string',
|
||||
description = 'Access Point mode SSID',
|
||||
min = 1,
|
||||
max = 32
|
||||
}
|
||||
|
||||
-- was: M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
||||
M.apAddress = {
|
||||
default = '192.168.10.1',
|
||||
type = 'string',
|
||||
description = 'Access Point mode IP address',
|
||||
regex = '%d+\.%d+\.%d+\.%d+'
|
||||
}
|
||||
|
||||
-- was: M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
||||
M.apNetmask = {
|
||||
default = '255.255.255.0',
|
||||
type = 'string',
|
||||
description = 'Access Point mode netmask',
|
||||
regex = '%d+\.%d+\.%d+\.%d+'
|
||||
}
|
||||
|
||||
--NOTE: proposed notation for baseline configuration (containing defaults as well as type and constraint information)
|
||||
--the table name is the configuration key; min, max and regex are all optional; type is one of: {int, float, string, ...?}
|
||||
M.temperature = {
|
||||
default = 230,
|
||||
type = 'int',
|
||||
description = '...xyzzy',
|
||||
description = '3D printer temperature',
|
||||
min = 0,
|
||||
max = 350
|
||||
}
|
||||
|
||||
M.ssid = {
|
||||
default = 'd3d-ap-%%MAC_TAIL%%',
|
||||
type = 'int', --one of: {int, float, string, ...?}
|
||||
min = 1,
|
||||
max = 32,
|
||||
regex = '[a-zA-Z0-9 -=+]+'
|
||||
}
|
||||
|
||||
|
||||
return M
|
||||
|
66
src/test/test_settings.lua
Normal file
66
src/test/test_settings.lua
Normal file
@ -0,0 +1,66 @@
|
||||
local s = require('util.settings')
|
||||
local defaults = require('conf_defaults')
|
||||
|
||||
local uciConfigFile = '/etc/config/wifibox'
|
||||
local uciConfigFileBackup = '/etc/config/wifibox.orig'
|
||||
|
||||
local M = {
|
||||
_is_test = true,
|
||||
_skip = { },
|
||||
_wifibox_only = { 'get' }
|
||||
}
|
||||
|
||||
|
||||
function M:_setup()
|
||||
os.execute('mv -f ' .. uciConfigFile .. ' ' .. uciConfigFileBackup .. ' 2>/dev/null')
|
||||
end
|
||||
|
||||
function M:_teardown()
|
||||
os.execute('rm -f ' .. uciConfigFile)
|
||||
os.execute('mv -f ' .. uciConfigFileBackup .. ' ' .. uciConfigFile .. ' 2>/dev/null')
|
||||
end
|
||||
|
||||
|
||||
function M:test_get()
|
||||
local realKey, fakeKey = 'apAddress', 'theAnswer'
|
||||
|
||||
assert(not s.exists(fakeKey))
|
||||
local fakeValue = s.get(fakeKey)
|
||||
assert(fakeValue == nil)
|
||||
|
||||
assert(s.exists(realKey))
|
||||
local realValue = s.get(realKey)
|
||||
assert(realValue ~= nil)
|
||||
assert(realValue == defaults.apAddress.default)
|
||||
end
|
||||
|
||||
function M:test_set()
|
||||
local key = 'apAddress'
|
||||
local goodValue, badValue1, badValue2 = '10.0.0.1', '10.00.1', '10.0.0d.1'
|
||||
|
||||
assert(s.get(key) == defaults.apAddress.default)
|
||||
assert(s.isDefault(key))
|
||||
|
||||
assert(s.set(key, goodValue))
|
||||
assert(s.get(key) == goodValue)
|
||||
assert(not s.isDefault(key))
|
||||
|
||||
assert(s.set(key, badValue1) == nil)
|
||||
assert(s.get(key) == goodValue)
|
||||
|
||||
assert(s.set(key, badValue2) == nil)
|
||||
assert(s.get(key) == goodValue)
|
||||
|
||||
assert(s.set(key, nil))
|
||||
assert(s.isDefault(key))
|
||||
end
|
||||
|
||||
function M:test_setNonExistent()
|
||||
local fakeKey = 'theAnswer'
|
||||
|
||||
assert(s.get(fakeKey) == nil)
|
||||
assert(s.set(fakeKey, 42) == nil)
|
||||
assert(s.get(fakeKey) == nil)
|
||||
end
|
||||
|
||||
return M
|
115
src/util/settings.lua
Normal file
115
src/util/settings.lua
Normal file
@ -0,0 +1,115 @@
|
||||
--[[
|
||||
This settings interface reads and writes its configuration using UCI.
|
||||
The corresponding config file is /etc/config/wifibox. To have an initial
|
||||
set of reasonable settings (and allow users to easily return to them),
|
||||
any key not found in the UCI configuration is looked up in the (immutable)
|
||||
'base configuration' (base_config.lua). This file also contains constraints
|
||||
to check if newly set values are valid.
|
||||
]]--
|
||||
local u = require('util.utils')
|
||||
local baseconfig = require('conf_defaults')
|
||||
local uci = require('uci').cursor()
|
||||
|
||||
local M = {}
|
||||
|
||||
local UCI_CONFIG_NAME = 'wifibox' -- the file under /etc/config
|
||||
local UCI_CONFIG_FILE = '/etc/config/' .. UCI_CONFIG_NAME
|
||||
local UCI_CONFIG_TYPE = 'settings' -- the section type that will be used in UCI_CONFIG_FILE
|
||||
local UCI_CONFIG_SECTION = 'general' -- the section name that will be used in UCI_CONFIG_FILE
|
||||
local ERR_NO_SUCH_KEY = "key does not exist"
|
||||
|
||||
|
||||
local function toUciValue(v, type)
|
||||
if type == 'bool' then return v and '1' or '0' end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
local function fromUciValue(v, type)
|
||||
if type == 'bool' then
|
||||
return (v == '1') and true or false
|
||||
elseif type == 'float' or type == 'int' then
|
||||
return tonumber(v)
|
||||
else
|
||||
return v
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function isValid(value, baseTable)
|
||||
local type, min, max, regex = baseTable.type, baseTable.min, baseTable.max, baseTable.regex
|
||||
|
||||
if type == 'bool' then
|
||||
return isboolean(value) or nil,"invalid bool value"
|
||||
elseif type == 'int' or type == 'float' then
|
||||
local ok = isnumber(value)
|
||||
ok = ok and (type == 'float' or math.floor(value) == value)
|
||||
if min then ok = ok and value >= min end
|
||||
if max then ok = ok and value <= max end
|
||||
return ok or nil,"invalid int/float value"
|
||||
elseif type == 'string' then
|
||||
local ok = true
|
||||
if min then ok = ok and value:len() >= min end
|
||||
if max then ok = ok and value:len() <= max end
|
||||
if regex then ok = ok and value:match(regex) ~= nil end
|
||||
return ok or nil,"invalid string value"
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function getBaseKeyTable(key)
|
||||
local base = baseconfig[key]
|
||||
return type(base) == 'table' and base.default ~= nil and base or nil
|
||||
end
|
||||
|
||||
|
||||
function M.get(key)
|
||||
local base = getBaseKeyTable(key)
|
||||
|
||||
if not base then return nil,ERR_NO_SUCH_KEY end
|
||||
|
||||
local v = base.default
|
||||
local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key))
|
||||
|
||||
return uciV or v
|
||||
end
|
||||
|
||||
function M.exists(key)
|
||||
return getBaseKeyTable(key) ~= nil
|
||||
end
|
||||
|
||||
function M.isDefault(key)
|
||||
if not M.exists(key) then return nil,ERR_NO_SUCH_KEY end
|
||||
return uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key) == nil
|
||||
end
|
||||
|
||||
-- pass nil as value to restore default
|
||||
function M.set(key, value)
|
||||
local r = u.create(UCI_CONFIG_FILE)
|
||||
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, UCI_CONFIG_TYPE)
|
||||
|
||||
local base = getBaseKeyTable(key)
|
||||
if not base then return nil,ERR_NO_SUCH_KEY end
|
||||
|
||||
if M.isDefault(key) and value == nil then return true end -- key is default already
|
||||
|
||||
local current = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
||||
|
||||
if fromUciValue(current) == value then return true end
|
||||
|
||||
if value ~= nil then
|
||||
local valid,m = isValid(value, base)
|
||||
if (valid) then
|
||||
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key, toUciValue(value, base.type))
|
||||
else
|
||||
return nil,m
|
||||
end
|
||||
else
|
||||
uci:delete(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)
|
||||
end
|
||||
|
||||
uci:commit(UCI_CONFIG_NAME)
|
||||
return true
|
||||
end
|
||||
|
||||
return M
|
Loading…
Reference in New Issue
Block a user