mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 11:03:48 +01:00
Add configuration keys for printing; rework configuration key naming.
This commit is contained in:
parent
596799472c
commit
9d6992ce95
@ -1,7 +1,18 @@
|
||||
local M = {}
|
||||
--[[
|
||||
This file contains all valid configuration keys, their default values and optional constraints.
|
||||
The table names are used as configuration key names, where underscores ('_') may be used to denote semi-categories.
|
||||
The settings interface replaces periods ('.') by underscores so for instance 'network.ap.address' will
|
||||
be translated to 'network_ap_address'. Multi-word names should be notated as camelCase.
|
||||
Valid fields for the tables are:
|
||||
- default: the default value (used when the key is not set in UCI config)
|
||||
- type: used for basic type checking, one of bool, int, float or string
|
||||
- description: A descriptive text usable by API clients
|
||||
- min, max, regex: optional constraints (min and max constrain value for numbers, or length for strings)
|
||||
|
||||
NOTE that the all-caps definitions will be changed into configuration keys, or moved to a different location
|
||||
]]--
|
||||
|
||||
--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}
|
||||
local M = {}
|
||||
|
||||
--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.
|
||||
@ -14,7 +25,7 @@ M.DEBUG_API = true
|
||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||
|
||||
|
||||
M.apSsid = {
|
||||
M.network_ap_ssid = {
|
||||
default = 'd3d-ap-%%MAC_ADDR_TAIL%%',
|
||||
type = 'string',
|
||||
description = 'Access Point mode SSID',
|
||||
@ -22,26 +33,133 @@ M.apSsid = {
|
||||
max = 32
|
||||
}
|
||||
|
||||
M.apAddress = {
|
||||
M.network_ap_address = {
|
||||
default = '192.168.10.1',
|
||||
type = 'string',
|
||||
description = 'Access Point mode IP address',
|
||||
regex = '%d+\.%d+\.%d+\.%d+'
|
||||
}
|
||||
|
||||
M.apNetmask = {
|
||||
M.network_ap_netmask = {
|
||||
default = '255.255.255.0',
|
||||
type = 'string',
|
||||
description = 'Access Point mode netmask',
|
||||
regex = '%d+\.%d+\.%d+\.%d+'
|
||||
}
|
||||
|
||||
M.temperature = {
|
||||
M.printer_temperature = {
|
||||
default = 230,
|
||||
type = 'int',
|
||||
description = '3D printer temperature',
|
||||
min = 0,
|
||||
max = 350
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_objectHeight = {
|
||||
default = 20,
|
||||
type = 'int',
|
||||
description = 'Maximum height that will be printed',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_layerHeight = {
|
||||
default = 0.2,
|
||||
type = 'float',
|
||||
description = '',
|
||||
min = 0.0
|
||||
}
|
||||
|
||||
M.printer_wallThickness = {
|
||||
default = 0.5,
|
||||
type = 'float',
|
||||
description = '',
|
||||
min = 0.0
|
||||
}
|
||||
|
||||
M.printer_speed = {
|
||||
default = 70,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_travelSpeed = {
|
||||
default = 200,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_filamentThickness = {
|
||||
default = 2.89,
|
||||
type = 'float',
|
||||
description = '',
|
||||
min = 0.0
|
||||
}
|
||||
|
||||
M.printer_useSubLayers = {
|
||||
default = true,
|
||||
type = 'bool',
|
||||
description = 'Continuously move platform while printing instead of once per layer'
|
||||
}
|
||||
|
||||
M.printer_firstLayerSlow = {
|
||||
default = true,
|
||||
type = 'float',
|
||||
description = 'Print the first layer slowly to get a more stable start',
|
||||
}
|
||||
|
||||
M.printer_autoWarmUp = {
|
||||
default = true,
|
||||
type = 'float',
|
||||
description = '',
|
||||
}
|
||||
|
||||
M.printer_simplify_iterations = {
|
||||
default = 10,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_simplify_minNumPoints = {
|
||||
default = 15,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_simplify_minDistance = {
|
||||
default = 3,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_retraction_speed = {
|
||||
default = 50,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_retraction_minDistance = {
|
||||
default = 5,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_retraction_amount = {
|
||||
default = 3,
|
||||
type = 'int',
|
||||
description = '',
|
||||
min = 0
|
||||
}
|
||||
|
||||
M.printer_autoWarmUpCommand = {
|
||||
default = 'M104 S230',
|
||||
type = 'string',
|
||||
description = ''
|
||||
}
|
||||
|
||||
return M
|
||||
|
@ -6,7 +6,7 @@ local uciConfigFileBackup = '/etc/config/wifibox.orig'
|
||||
|
||||
local M = {
|
||||
_is_test = true,
|
||||
_skip = { },
|
||||
_skip = { 'constraints' }, --FIXME: enabling constraints 'breaks' other tests
|
||||
_wifibox_only = { 'get' }
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ end
|
||||
|
||||
|
||||
function M:test_get()
|
||||
local realKey, fakeKey = 'apAddress', 'theAnswer'
|
||||
local realKey, fakeKey = 'network_ap_address', 'theQuestion'
|
||||
|
||||
assert(not s.exists(fakeKey))
|
||||
local fakeValue = s.get(fakeKey)
|
||||
@ -31,40 +31,80 @@ function M:test_get()
|
||||
assert(s.exists(realKey))
|
||||
local realValue = s.get(realKey)
|
||||
assert(realValue ~= nil)
|
||||
assert(realValue == defaults.apAddress.default)
|
||||
assert(realValue == defaults.network_ap_address.default)
|
||||
end
|
||||
|
||||
function M:test_set()
|
||||
local key, intKey = 'apAddress', 'temperature'
|
||||
local intValue, goodValue, badValue1, badValue2 = 340, '10.0.0.1', '10.00.1', '10.0.0d.1'
|
||||
local key, intKey, floatKey, boolKey = 'network_ap_address', 'printer_temperature', 'printer_filamentThickness', 'printer_useSubLayers'
|
||||
local intValue, floatValue, boolValue = 340, 4.2, false
|
||||
local value = '10.0.0.1'
|
||||
|
||||
assert(s.get(key) == defaults.apAddress.default)
|
||||
assert(s.get(key) == defaults.network_ap_address.default)
|
||||
assert(s.isDefault(key))
|
||||
|
||||
assert(s.set(key, goodValue))
|
||||
assert(s.get(key) == goodValue)
|
||||
assert(s.set(key, value))
|
||||
assert(s.get(key) == value)
|
||||
assert(not s.isDefault(key))
|
||||
|
||||
assert(s.set(key, nil))
|
||||
assert(s.isDefault(key))
|
||||
|
||||
-- test with value of int type
|
||||
assert(s.get(intKey) == defaults.printer_temperature.default)
|
||||
assert(s.isDefault(intKey))
|
||||
|
||||
assert(s.set(intKey, intValue))
|
||||
assert(s.get(intKey) == intValue)
|
||||
assert(not s.isDefault(intKey))
|
||||
|
||||
-- test with value of float type
|
||||
assert(s.get(floatKey) == defaults.printer_filamentThickness.default)
|
||||
assert(s.isDefault(floatKey))
|
||||
|
||||
assert(s.set(floatKey, floatValue))
|
||||
assert(s.get(floatKey) == floatValue)
|
||||
assert(not s.isDefault(floatKey))
|
||||
|
||||
-- test with value of bool type
|
||||
assert(s.get(boolKey) == defaults.printer_useSubLayers.default)
|
||||
assert(s.isDefault(boolKey))
|
||||
|
||||
assert(s.set(boolKey, boolValue))
|
||||
assert(s.get(boolKey) == boolValue)
|
||||
assert(not s.isDefault(boolKey))
|
||||
end
|
||||
|
||||
function M:test_dotsReplacement()
|
||||
local underscoredKey, dottedKey, mixedKey = 'printer_retraction_speed', 'printer.retraction.speed', 'printer.retraction_speed'
|
||||
|
||||
assert(s.get(underscoredKey) == defaults.printer_retraction_speed.default)
|
||||
assert(s.get(dottedKey) == defaults.printer_retraction_speed.default)
|
||||
assert(s.get(mixedKey) == defaults.printer_retraction_speed.default)
|
||||
|
||||
assert(s.set(mixedKey, 54321))
|
||||
assert(s.get(underscoredKey) == 54321)
|
||||
assert(s.get(dottedKey) == 54321)
|
||||
end
|
||||
|
||||
function M:test_constraints()
|
||||
local key, key2 = 'network_ap_address', 'printer_temperature'
|
||||
local goodValue, badValue1, badValue2 = '10.0.0.1', '10.00.1', '10.0.0d.1'
|
||||
|
||||
assert(s.set(key, goodValue))
|
||||
|
||||
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))
|
||||
|
||||
-- test with value of int type
|
||||
assert(s.get(intKey) == defaults.temperature.default)
|
||||
assert(s.isDefault(intKey))
|
||||
|
||||
assert(s.set(intKey, intValue))
|
||||
assert(s.get(intKey) == intValue)
|
||||
assert(not s.isDefault(intKey))
|
||||
assert(s.get(key2) == defaults.printer_temperature.default)
|
||||
assert(s.set(key2, -1) == nil)
|
||||
assert(s.get(key2) == defaults.printer_temperature.default)
|
||||
end
|
||||
|
||||
function M:test_setNonExistent()
|
||||
local fakeKey = 'theAnswer'
|
||||
local fakeKey = 'theQuestion'
|
||||
|
||||
assert(s.get(fakeKey) == nil)
|
||||
assert(s.set(fakeKey, 42) == nil)
|
||||
|
@ -1,12 +1,15 @@
|
||||
--[[
|
||||
This settings interface reads and writes its configuration using UCI.
|
||||
The 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.
|
||||
|
||||
By the way, returning correct values in get()/fromUciValue() for booleans has been fixed at a
|
||||
relatively convenient time purely thanks to the unit tests...just to indicate they are useful. :)
|
||||
]]--
|
||||
local u = require('util.utils')
|
||||
local utils = require('util.utils')
|
||||
local baseconfig = require('conf_defaults')
|
||||
local uci = require('uci').cursor()
|
||||
|
||||
@ -19,15 +22,25 @@ local UCI_CONFIG_SECTION = 'general' -- the section name that will be used in UC
|
||||
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
|
||||
--- Returns the given key with all periods ('.') replaced by underscores ('_').
|
||||
-- @param key The key for which to substitute dots.
|
||||
-- @return The substituted key, or the key parameter itself if it is not of type 'string'.
|
||||
local function replaceDots(key)
|
||||
if type(key) ~= 'string' then return key end
|
||||
return key:gsub('%.', '_')
|
||||
end
|
||||
|
||||
local function toUciValue(v, vType)
|
||||
if vType == 'bool' then return v and '1' or '0' end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
local function fromUciValue(v, type)
|
||||
if type == 'bool' then
|
||||
local function fromUciValue(v, vType)
|
||||
if v == nil then return nil end
|
||||
|
||||
if vType == 'bool' then
|
||||
return (v == '1') and true or false
|
||||
elseif type == 'float' or type == 'int' then
|
||||
elseif vType == 'float' or vType == 'int' then
|
||||
return tonumber(v)
|
||||
else
|
||||
return v
|
||||
@ -36,20 +49,20 @@ local function fromUciValue(v, type)
|
||||
end
|
||||
|
||||
local function isValid(value, baseTable)
|
||||
local type, min, max, regex = baseTable.type, baseTable.min, baseTable.max, baseTable.regex
|
||||
local varType, min, max, regex = baseTable.type, baseTable.min, baseTable.max, baseTable.regex
|
||||
|
||||
if type == 'bool' then
|
||||
return isboolean(value) or nil,"invalid bool value"
|
||||
if varType == 'bool' then
|
||||
return type(value) == 'boolean' or nil,"invalid bool value"
|
||||
|
||||
elseif type == 'int' or type == 'float' then
|
||||
elseif varType == 'int' or varType == 'float' then
|
||||
local numValue = tonumber(value)
|
||||
local ok = numValue and true or false
|
||||
ok = ok and (type == 'float' or math.floor(numValue) == numValue)
|
||||
ok = ok and (varType == 'float' or math.floor(numValue) == numValue)
|
||||
if min then ok = ok and numValue >= min end
|
||||
if max then ok = ok and numValue <= max end
|
||||
return ok or nil,"invalid int/float value or out of range"
|
||||
|
||||
elseif type == 'string' then
|
||||
elseif varType == '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
|
||||
@ -66,7 +79,11 @@ local function getBaseKeyTable(key)
|
||||
end
|
||||
|
||||
|
||||
--- Returns the value of the requested key if it exists.
|
||||
-- @param key The key to return the associated value for.
|
||||
-- @return The associated value, beware (!) that this may be boolean false for keys of 'bool' type.
|
||||
function M.get(key)
|
||||
key = replaceDots(key)
|
||||
local base = getBaseKeyTable(key)
|
||||
|
||||
if not base then return nil,ERR_NO_SUCH_KEY end
|
||||
@ -74,21 +91,27 @@ function M.get(key)
|
||||
local v = base.default
|
||||
local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key), base.type)
|
||||
|
||||
return uciV or v
|
||||
local actualV = v
|
||||
if uciV ~= nil then actualV = uciV end
|
||||
|
||||
return actualV
|
||||
end
|
||||
|
||||
function M.exists(key)
|
||||
key = replaceDots(key)
|
||||
return getBaseKeyTable(key) ~= nil
|
||||
end
|
||||
|
||||
function M.isDefault(key)
|
||||
key = replaceDots(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)
|
||||
key = replaceDots(key)
|
||||
local r = utils.create(UCI_CONFIG_FILE)
|
||||
uci:set(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, UCI_CONFIG_TYPE)
|
||||
|
||||
local base = getBaseKeyTable(key)
|
||||
|
Loading…
Reference in New Issue
Block a user