mirror of
https://github.com/joe7575/techpack.git
synced 2024-11-25 16:43:50 +01:00
New controller based on SaferLUA
This commit is contained in:
parent
16f62e30bc
commit
eb0ea4602a
0
safer_lua/depends.txt
Normal file
0
safer_lua/depends.txt
Normal file
4
safer_lua/description.txt
Normal file
4
safer_lua/description.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
SaferLUA [safer_lua], a subset of the language LUA for safe and secure LUA sandboxes
|
||||||
|
|
||||||
|
|
||||||
|
|
97
safer_lua/environ.lua
Normal file
97
safer_lua/environ.lua
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
safer_lua.MaxCodeSize = 1000 -- size in length of byte code
|
||||||
|
safer_lua.MaxTableSize = 1000 -- number of table entries considering string lenghts
|
||||||
|
|
||||||
|
|
||||||
|
local BASE_ENV = {
|
||||||
|
Store = safer_lua.Store,
|
||||||
|
math = {
|
||||||
|
floor = math.floor
|
||||||
|
},
|
||||||
|
ticks = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function map(dest, source)
|
||||||
|
for k,v in pairs(source) do
|
||||||
|
dest[k] = v
|
||||||
|
end
|
||||||
|
return dest
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua.config(max_code_size, max_table_size)
|
||||||
|
safer_lua.MaxCodeSize = max_code_size
|
||||||
|
safer_lua.MaxTableSize = max_table_size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compile(pos, text, label, err_clbk)
|
||||||
|
if safer_lua:check(text, label, err_clbk) == 0 then
|
||||||
|
text = text:gsub("%$", "S:")
|
||||||
|
local code, err = loadstring(text)
|
||||||
|
if not code then
|
||||||
|
err = err:gsub("%[string .+%]:", label)
|
||||||
|
err_clbk(pos, err)
|
||||||
|
else
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua.init(pos, init, loop, environ, err_clbk)
|
||||||
|
if #init > safer_lua.MaxCodeSize then
|
||||||
|
err_clbk(pos, "init() Code size limit exceeded")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if #loop > safer_lua.MaxCodeSize then
|
||||||
|
err_clbk(pos, "loop() Code size limit exceeded")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local code = compile(pos, init, "init() ", err_clbk)
|
||||||
|
if code then
|
||||||
|
local env = BASE_ENV
|
||||||
|
env.S = {}
|
||||||
|
env.S._G = _G
|
||||||
|
env.S = map(env.S, environ)
|
||||||
|
setfenv(code, env)
|
||||||
|
local res, err = pcall(code)
|
||||||
|
if not res then
|
||||||
|
err = err:gsub("%[string .+%]:", "init() ")
|
||||||
|
err_clbk(pos, err)
|
||||||
|
else
|
||||||
|
env = getfenv(code)
|
||||||
|
code = compile(pos, loop, "loop() ", err_clbk)
|
||||||
|
if code then
|
||||||
|
setfenv(code, env)
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua.run_loop(pos, elapsed, code, err_clbk)
|
||||||
|
local env = getfenv(code)
|
||||||
|
env.event = false
|
||||||
|
env.ticks = env.ticks + 1
|
||||||
|
env.elapsed = elapsed
|
||||||
|
setfenv(code, env)
|
||||||
|
local res, err = pcall(code)
|
||||||
|
if not res then
|
||||||
|
err = err:gsub("%[string .+%]:", "loop() ")
|
||||||
|
err_clbk(pos, err)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua.run_event(pos, code, err_clbk)
|
||||||
|
local env = getfenv(code)
|
||||||
|
env.event = true
|
||||||
|
setfenv(code, env)
|
||||||
|
local res, err = pcall(code)
|
||||||
|
if not res then
|
||||||
|
err = err:gsub("%[string .+%]:", "loop() ")
|
||||||
|
err_clbk(pos, err)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
17
safer_lua/init.lua
Normal file
17
safer_lua/init.lua
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
SaferLUA
|
||||||
|
========
|
||||||
|
|
||||||
|
Copyright (C) 2018 Joachim Stolberg
|
||||||
|
|
||||||
|
LGPLv2.1+
|
||||||
|
See LICENSE.txt for more information
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
safer_lua = {}
|
||||||
|
|
||||||
|
dofile(minetest.get_modpath("safer_lua") .. "/store.lua")
|
||||||
|
dofile(minetest.get_modpath("safer_lua") .. "/scanner.lua")
|
||||||
|
dofile(minetest.get_modpath("safer_lua") .. "/environ.lua")
|
1
safer_lua/mod.conf
Normal file
1
safer_lua/mod.conf
Normal file
@ -0,0 +1 @@
|
|||||||
|
name=safer_lua
|
11
safer_lua/readme.md
Normal file
11
safer_lua/readme.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
SaferLUA [safer_lua]
|
||||||
|
====================
|
||||||
|
|
||||||
|
A subset of the language LUA for safe and secure LUA sandboxes with:
|
||||||
|
- limited code length
|
||||||
|
- limited execution time
|
||||||
|
- limited memory use
|
||||||
|
- limited posibilities to call functions
|
||||||
|
|
||||||
|
|
||||||
|
|
94
safer_lua/scanner.lua
Normal file
94
safer_lua/scanner.lua
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
local function trim(s)
|
||||||
|
return (s:gsub("^%s*(.-)%s*$", "%1"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua:word(ch, pttrn)
|
||||||
|
local word = ""
|
||||||
|
while ch:match(pttrn) do
|
||||||
|
word = word .. ch
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
ch = self.line:sub(self.pos, self.pos)
|
||||||
|
end
|
||||||
|
return word
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua:string(pttrn)
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
local ch = self.line:sub(self.pos, self.pos)
|
||||||
|
while not ch:match(pttrn) and self.pos < #self.line do
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
ch = self.line:sub(self.pos, self.pos)
|
||||||
|
end
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
-- result is not needed
|
||||||
|
end
|
||||||
|
|
||||||
|
function safer_lua:scanner(text)
|
||||||
|
local lToken = {}
|
||||||
|
for idx, line in ipairs(text:split("\n")) do
|
||||||
|
self.line = line
|
||||||
|
self.pos = 1
|
||||||
|
self.line = trim(self.line)
|
||||||
|
self.line = self.line:split("--")[1]
|
||||||
|
if self.line then
|
||||||
|
-- devide line in tokens
|
||||||
|
table.insert(lToken, idx) -- line number
|
||||||
|
while true do
|
||||||
|
if self.pos > #self.line then break end
|
||||||
|
local ch = self.line:sub(self.pos, self.pos)
|
||||||
|
if ch:match("[%u%l_]") then -- identifier?
|
||||||
|
table.insert(lToken, self:word(ch, "[%w_]"))
|
||||||
|
elseif ch:match("[%d]") then -- number?
|
||||||
|
table.insert(lToken, self:word(ch, "[%d%xx]"))
|
||||||
|
elseif ch:match("'") then -- string?
|
||||||
|
self:string("'")
|
||||||
|
elseif ch:match('"') then -- string?
|
||||||
|
self:string('"')
|
||||||
|
elseif ch:match("[%s]") then -- Space?
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
elseif ch:match("[:{}]") then -- critical tokens?
|
||||||
|
table.insert(lToken,ch)
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
else
|
||||||
|
self.pos = self.pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return lToken
|
||||||
|
end
|
||||||
|
|
||||||
|
local InvalidKeywords = {
|
||||||
|
["while"] = true,
|
||||||
|
["repeat"] = true,
|
||||||
|
["break"] = true,
|
||||||
|
["until"] = true,
|
||||||
|
["for"] = true,
|
||||||
|
["do"] = true,
|
||||||
|
["function"] = true,
|
||||||
|
["_G"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local InvalidChars = {
|
||||||
|
[":"] = true,
|
||||||
|
["{"] = true,
|
||||||
|
["}"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
function safer_lua:check(text, label, err_clbk)
|
||||||
|
local lToken = self:scanner(text)
|
||||||
|
local lineno = 0
|
||||||
|
local errno = 0
|
||||||
|
for _,token in ipairs(lToken) do
|
||||||
|
if type(token) == "number" then
|
||||||
|
lineno = token
|
||||||
|
elseif InvalidKeywords[token] then
|
||||||
|
err_clbk(label..lineno..": Invalid keyword '"..token.."'")
|
||||||
|
errno = errno + 1
|
||||||
|
elseif InvalidChars[token] then
|
||||||
|
err_clbk(label..lineno..": Invalid character '"..token.."'")
|
||||||
|
errno = errno + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return errno
|
||||||
|
end
|
118
safer_lua/store.lua
Normal file
118
safer_lua/store.lua
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
safer_lua.StoreHelp = [[
|
||||||
|
Store - a secure shell over the LUA table type.
|
||||||
|
|
||||||
|
For records:
|
||||||
|
tbl = Store() --> {}
|
||||||
|
tbl.set(key,value) --> {key=value}
|
||||||
|
tbl.get(key) --> value
|
||||||
|
|
||||||
|
'key' can be a number or string
|
||||||
|
'value' can be number, string, boolean, or Store
|
||||||
|
Example: tbl.set("a","test")
|
||||||
|
|
||||||
|
For lists:
|
||||||
|
tbl = Store(1,2,3,4) --> {1,2,3,4}
|
||||||
|
tbl.insert(pos, value)
|
||||||
|
tbl.remove(pos)
|
||||||
|
|
||||||
|
'pos' must be a number
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
tbl.set(key, value) --> add/set a value
|
||||||
|
tbl.get(key) --> read a value
|
||||||
|
tbl.size() --> return the table size
|
||||||
|
tbl.insert(pos, value) --> insert into list
|
||||||
|
tbl.remove(pos) --> return and remove from list
|
||||||
|
tbl.sort() -- sort list
|
||||||
|
tbl.dump() --> format as string (debugging)
|
||||||
|
]]
|
||||||
|
|
||||||
|
function safer_lua.Store(...)
|
||||||
|
|
||||||
|
local new_t = {__data__ = {}}
|
||||||
|
local mt = {}
|
||||||
|
|
||||||
|
-- `all` will represent the number of both
|
||||||
|
local Count = 0
|
||||||
|
|
||||||
|
mt.__newindex = function(t, k, v) return end
|
||||||
|
|
||||||
|
mt.count = function(v)
|
||||||
|
if type(v) == "number" then
|
||||||
|
return 1
|
||||||
|
elseif type(v) == "boolean" then
|
||||||
|
return 1
|
||||||
|
elseif v == nil then
|
||||||
|
return 0
|
||||||
|
elseif type(v) == "string" then
|
||||||
|
return #v
|
||||||
|
elseif type(v) == "table" then
|
||||||
|
return v.size()
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for idx = 1,select('#',...) do
|
||||||
|
local v = select(idx,...)
|
||||||
|
local cnt = mt.count(v)
|
||||||
|
if cnt then
|
||||||
|
Count = Count + cnt
|
||||||
|
if Count < safer_lua.MaxTableSize then
|
||||||
|
rawset(new_t.__data__,idx, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.set = function(k,v)
|
||||||
|
if type(k) == "string" or type(k) == "number" then
|
||||||
|
Count = Count - mt.count(rawget(new_t.__data__, k))
|
||||||
|
local cnt = mt.count(v)
|
||||||
|
if cnt then
|
||||||
|
Count = Count + cnt
|
||||||
|
if Count < safer_lua.MaxTableSize then
|
||||||
|
rawset(new_t.__data__,k,v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.get = function(k)
|
||||||
|
return rawget(new_t.__data__, k)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.size = function(t)
|
||||||
|
return Count
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.insert = function(v, i)
|
||||||
|
local cnt = mt.count(v)
|
||||||
|
if cnt then
|
||||||
|
Count = Count + cnt
|
||||||
|
if i == nil then i = #new_t.__data__ + 1 end
|
||||||
|
if Count < safer_lua.MaxTableSize then
|
||||||
|
table.insert(new_t.__data__,i,v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.remove = function(i)
|
||||||
|
local v = table.remove(new_t.__data__,i)
|
||||||
|
local cnt = mt.count(v)
|
||||||
|
Count = Count - cnt
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.sort = function()
|
||||||
|
table.sort(new_t.__data__)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_t.dump = function(size)
|
||||||
|
size = size or 200
|
||||||
|
local s = dump(new_t.__data__)
|
||||||
|
if #s > size then s = s:sub(1, size).."..." end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(new_t, mt)
|
||||||
|
end
|
49
safer_lua/test_environ.lua
Normal file
49
safer_lua/test_environ.lua
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
core = {}
|
||||||
|
|
||||||
|
function core.global_exists(name)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile('/home/joachim/minetest/builtin/common/vector.lua')
|
||||||
|
dofile('/home/joachim/minetest/builtin/common/misc_helpers.lua')
|
||||||
|
|
||||||
|
safer_lua = {}
|
||||||
|
dofile('/home/joachim/minetest/mods/techpack/safer_lua/store.lua')
|
||||||
|
dofile('/home/joachim/minetest/mods/techpack/safer_lua/scanner.lua')
|
||||||
|
dofile('/home/joachim/minetest/mods/techpack/safer_lua/environ.lua')
|
||||||
|
|
||||||
|
--local Cache = {}
|
||||||
|
--local key = minetest.pos_to_hash(pos)
|
||||||
|
--code = Cache[key]
|
||||||
|
|
||||||
|
local function foo(self, val)
|
||||||
|
_G = self._G
|
||||||
|
print("Hallo", val)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function error(pos, s)
|
||||||
|
print("[Test] "..s)
|
||||||
|
end
|
||||||
|
|
||||||
|
local init = "init = 5"
|
||||||
|
local loop = [[
|
||||||
|
$foo("hallo")
|
||||||
|
S.foo("hallo")
|
||||||
|
--S._G.print("Fehler")
|
||||||
|
$foo(math.floor(5.5))
|
||||||
|
$foo("Joe")
|
||||||
|
a = Store()
|
||||||
|
a.set("a", 123)
|
||||||
|
$foo(a.get("a"))
|
||||||
|
$foo(ticks)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local env = {foo = foo}
|
||||||
|
|
||||||
|
|
||||||
|
local code = safer_lua.init(0, init, loop, env, error)
|
||||||
|
if code then
|
||||||
|
print(safer_lua.run_loop(0, code, error))
|
||||||
|
safer_lua.run_loop(0, code, error)
|
||||||
|
safer_lua.run_loop(0, code, error)
|
||||||
|
end
|
28
safer_lua/test_scanner.lua
Normal file
28
safer_lua/test_scanner.lua
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
core = {}
|
||||||
|
|
||||||
|
function core.global_exists(name)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile('/home/joachim/minetest/builtin/common/vector.lua')
|
||||||
|
dofile('/home/joachim/minetest/builtin/common/misc_helpers.lua')
|
||||||
|
dofile('/home/joachim/minetest/mods/techpack/safer_lua/scanner.lua')
|
||||||
|
|
||||||
|
code = [[
|
||||||
|
-- GOOD
|
||||||
|
a = 1
|
||||||
|
a = a + 1
|
||||||
|
print(a)
|
||||||
|
foo(a)
|
||||||
|
|
||||||
|
-- BAD
|
||||||
|
_G.print(()
|
||||||
|
t = {}
|
||||||
|
for i = 1,1000 do
|
||||||
|
]]
|
||||||
|
|
||||||
|
local function error(s)
|
||||||
|
print("[Robbi] "..s)
|
||||||
|
end
|
||||||
|
|
||||||
|
safer_lua:check(code, "Code", error)
|
57
safer_lua/test_store.lua
Normal file
57
safer_lua/test_store.lua
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
core = {}
|
||||||
|
|
||||||
|
function core.global_exists(name)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile('/home/joachim/minetest/builtin/common/vector.lua')
|
||||||
|
dofile('/home/joachim/minetest/builtin/common/misc_helpers.lua')
|
||||||
|
|
||||||
|
safer_lua = {}
|
||||||
|
safer_lua.MaxTableSize = 1000 -- number of table entries considering string lenghts
|
||||||
|
|
||||||
|
dofile('/home/joachim/minetest/mods/techpack/safer_lua/store.lua')
|
||||||
|
|
||||||
|
|
||||||
|
print("S1")
|
||||||
|
local s1 = safer_lua.Store()
|
||||||
|
assert(s1.size() == 0)
|
||||||
|
|
||||||
|
s1.a = 3
|
||||||
|
s1[1] = 4
|
||||||
|
assert(s1.size() == 0)
|
||||||
|
|
||||||
|
s1.set("b", "Hallo")
|
||||||
|
assert(s1.size() == 5)
|
||||||
|
|
||||||
|
assert(s1.get("b") == "Hallo")
|
||||||
|
assert(s1.size() == 5)
|
||||||
|
|
||||||
|
print("S2")
|
||||||
|
local s2 = safer_lua.Store()
|
||||||
|
assert(s2.size() == 0)
|
||||||
|
s2.set("b", "Joe")
|
||||||
|
assert(s2.size() == 3)
|
||||||
|
|
||||||
|
assert(s2.b == nil)
|
||||||
|
assert(s2.get('b') == "Joe")
|
||||||
|
s2.c = "XXX!"
|
||||||
|
assert(s2.c == nil)
|
||||||
|
|
||||||
|
s1.set("c", s2)
|
||||||
|
print(dump(s1.get("c")))
|
||||||
|
|
||||||
|
print("S3")
|
||||||
|
local s3 = safer_lua.Store(1,2,3,4)
|
||||||
|
assert(s3.size() == 4)
|
||||||
|
print(dump(s3))
|
||||||
|
|
||||||
|
s3.insert(0, 1)
|
||||||
|
s3.insert(5)
|
||||||
|
print(s3.dump())
|
||||||
|
print(s2.dump())
|
||||||
|
|
||||||
|
s2.set("s2", s2)
|
||||||
|
print(s2.dump())
|
||||||
|
|
||||||
|
print(dump(s2))
|
143
sl_controller/commands.lua
Normal file
143
sl_controller/commands.lua
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
sl_controller
|
||||||
|
=============
|
||||||
|
|
||||||
|
Copyright (C) 2018 Joachim Stolberg
|
||||||
|
|
||||||
|
LGPLv2.1+
|
||||||
|
See LICENSE.txt for more information
|
||||||
|
|
||||||
|
commands.lua:
|
||||||
|
|
||||||
|
Register all basic controller commands
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
sl_controller.register_function("get_input", {
|
||||||
|
cmnd = function(self, num)
|
||||||
|
_G = self._G
|
||||||
|
return sl_controller.get_input(self.meta.number, num)
|
||||||
|
end,
|
||||||
|
help = " $get_input(num) --> 'on', 'off', or nil\n"..
|
||||||
|
" Read local input value from device with number 'num'.\n"..
|
||||||
|
' example: inp = $get_input("1234")\n'..
|
||||||
|
" The device has to be connected with the controller."
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_function("get_status", {
|
||||||
|
cmnd = function(self, num)
|
||||||
|
_G = self._G
|
||||||
|
return tubelib.send_request(num, "state", nil)
|
||||||
|
end,
|
||||||
|
help = " $get_status(num) --> 'stopped', 'running',\n"..
|
||||||
|
" 'standby', 'blocked' or 'fault'\n"..
|
||||||
|
" Read status from a remote device.\n"..
|
||||||
|
' example: sts = $get_status("1234")'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_function("time_as_str", {
|
||||||
|
cmnd = function(self)
|
||||||
|
_G = self._G
|
||||||
|
local t = minetest.get_timeofday()
|
||||||
|
local h = math.floor(t*24) % 24
|
||||||
|
local m = math.floor(t*1440) % 60
|
||||||
|
return string.format("%02d:%02d", h, m)
|
||||||
|
end,
|
||||||
|
help = " $time_as_str() --> e.g. '18:45'\n"..
|
||||||
|
" Read time of day as string (24h).\n"..
|
||||||
|
' example: time = $time_as_str()'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_function("time_as_num", {
|
||||||
|
cmnd = function(self, num)
|
||||||
|
_G = self._G
|
||||||
|
local t = minetest.get_timeofday()
|
||||||
|
local h = math.floor(t*24) % 24
|
||||||
|
local m = math.floor(t*1440) % 60
|
||||||
|
return h * 100 + m
|
||||||
|
end,
|
||||||
|
help = " $time_as_num() --> e.g.: 1845\n"..
|
||||||
|
" Read time of day as number (24h).\n"..
|
||||||
|
' example: time = $time_as_num()'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_function("playerdetector", {
|
||||||
|
cmnd = function(self, num)
|
||||||
|
_G = self._G
|
||||||
|
return tubelib.send_request(num, "name", nil)
|
||||||
|
end,
|
||||||
|
help = " $playerdetector(num) --> e.g. 'Joe'\n"..
|
||||||
|
" '' is returned if no layer is nearby.\n"..
|
||||||
|
' example: name = $playerdetector("1234")'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_action("send_cmnd", {
|
||||||
|
cmnd = function(self, num, text)
|
||||||
|
_G = self._G
|
||||||
|
tubelib.send_message(num, self.meta.owner, nil, text, nil)
|
||||||
|
end,
|
||||||
|
help = " $send_cmnd(num, text)\n"..
|
||||||
|
" Send a command to the device with number 'num'.\n"..
|
||||||
|
" For most devices: 'on', 'off'\n"..
|
||||||
|
" Signal Tower: 'green', 'amber', 'red'\n"..
|
||||||
|
' example: $send_cmnd("1234", "on")'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_action("display", {
|
||||||
|
cmnd = function(self, num, row, text1, text2, text3)
|
||||||
|
_G = self._G
|
||||||
|
local text = (text1 or "") .. (text2 or "") .. (text3 or "")
|
||||||
|
tubelib.send_message(num, self.meta.owner, nil, "row", {row = row, str = text})
|
||||||
|
end,
|
||||||
|
help = " $display(num, row, text,...)\n"..
|
||||||
|
" Send a text line to the display with number 'num'.\n"..
|
||||||
|
" 'row' is a value from 1..9\n"..
|
||||||
|
" The function accepts up to 3 text parameters\n"..
|
||||||
|
' example: $display("0123", 1, "Hello ", name, " !")'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_action("clear_screen", {
|
||||||
|
cmnd = function(self, num)
|
||||||
|
_G = self._G
|
||||||
|
tubelib.send_message(num, self.meta.owner, nil, "clear", nil)
|
||||||
|
end,
|
||||||
|
help = " $clear_screen(num)\n"..
|
||||||
|
" Clear the screen of the display\n"..
|
||||||
|
" with number 'num'.\n"..
|
||||||
|
' example: $clear_screen("1234")'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_action("chat", {
|
||||||
|
cmnd = function(self, text)
|
||||||
|
_G = self._G
|
||||||
|
minetest.chat_send_player(self.meta.owner, "[SmartLine Controller] "..text)
|
||||||
|
end,
|
||||||
|
help = " $chat(text)\n"..
|
||||||
|
" Send yourself a chat message.\n"..
|
||||||
|
' example: $chat("Hello")'
|
||||||
|
})
|
||||||
|
|
||||||
|
sl_controller.register_action("door", {
|
||||||
|
cmnd = function(self, pos, text)
|
||||||
|
_G = self._G
|
||||||
|
pos = minetest.string_to_pos("("..pos..")")
|
||||||
|
if pos then
|
||||||
|
local door = doors.get(pos)
|
||||||
|
if door then
|
||||||
|
local player = {
|
||||||
|
get_player_name = function() return self.meta.owner end,
|
||||||
|
}
|
||||||
|
if state == "open" then
|
||||||
|
door:open(player)
|
||||||
|
elseif state == "close" then
|
||||||
|
door:close(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
help = " %door(pos, text)\n"..
|
||||||
|
" Open/Close a door at position 'pos'\n"..
|
||||||
|
' example: door("123,7,-1200", "close")\n'..
|
||||||
|
" Hint: Use the Tubelib Programmer to\ndetermine the door position."
|
||||||
|
})
|
371
sl_controller/controller.lua
Normal file
371
sl_controller/controller.lua
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
sl_controller
|
||||||
|
=============
|
||||||
|
|
||||||
|
Copyright (C) 2018 Joachim Stolberg
|
||||||
|
|
||||||
|
LGPLv2.1+
|
||||||
|
See LICENSE.txt for more information
|
||||||
|
|
||||||
|
controller.lua:
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local sHELP = [[Safer LUA Controller
|
||||||
|
|
||||||
|
Safer LUA is a subset of LUA with the following restrictions:
|
||||||
|
- No loop keywords like: for, while, repeat,...
|
||||||
|
- No table constructions via: { ... }
|
||||||
|
- Limited set of available functions
|
||||||
|
- Store() as alternative to LUA tables
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
local mail_exists = minetest.get_modpath("mail") and mail ~= nil
|
||||||
|
|
||||||
|
sl_controller = {}
|
||||||
|
|
||||||
|
local Cache = {}
|
||||||
|
|
||||||
|
local tCommands = {}
|
||||||
|
local tFunctions = {" Overview", " Store"}
|
||||||
|
local tHelpTexts = {[" Overview"] = sHELP, [" Store"] = safer_lua.StoreHelp}
|
||||||
|
local sFunctionList = ""
|
||||||
|
local tFunctionIndex = {}
|
||||||
|
|
||||||
|
minetest.after(2, function()
|
||||||
|
sFunctionList = table.concat(tFunctions, ",")
|
||||||
|
for idx,key in ipairs(tFunctions) do
|
||||||
|
tFunctionIndex[key] = idx
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function output(pos, text)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
text = meta:get_string("output") .. "\n" .. (text or "")
|
||||||
|
text = text:sub(-500,-1)
|
||||||
|
meta:set_string("output", text)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- API functions for function/action registrations
|
||||||
|
--
|
||||||
|
function sl_controller.register_function(key, attr)
|
||||||
|
tCommands[key] = attr.cmnd
|
||||||
|
table.insert(tFunctions, " $"..key)
|
||||||
|
tHelpTexts[" $"..key] = attr.help
|
||||||
|
end
|
||||||
|
|
||||||
|
function sl_controller.register_action(key, attr)
|
||||||
|
tCommands[key] = attr.cmnd
|
||||||
|
table.insert(tFunctions, " $"..key)
|
||||||
|
tHelpTexts[" $"..key] = attr.help
|
||||||
|
end
|
||||||
|
|
||||||
|
local function merge(dest, keys, values)
|
||||||
|
for idx,key in ipairs(keys) do
|
||||||
|
dest.env[key] = values[idx]
|
||||||
|
end
|
||||||
|
return dest
|
||||||
|
end
|
||||||
|
|
||||||
|
sl_controller.register_action("print", {
|
||||||
|
cmnd = function(self, text1, text2, text3)
|
||||||
|
_G = self._G
|
||||||
|
local pos = self.meta.pos
|
||||||
|
local text = (text1 or "")..(text2 or "")..(text3 or "")
|
||||||
|
output(pos, text)
|
||||||
|
end,
|
||||||
|
help = "$print(text,...)\n"..
|
||||||
|
"Send a text line to the output window.\n"..
|
||||||
|
"The function accepts up to 3 text strings\n"..
|
||||||
|
'e.g. $print("Hello ", name, " !")'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
local function formspec1(meta)
|
||||||
|
local running = meta:get_int("state") == tubelib.RUNNING
|
||||||
|
local cmnd = running and "stop;Stop" or "start;Start"
|
||||||
|
local init = meta:get_string("init")
|
||||||
|
init = minetest.formspec_escape(init)
|
||||||
|
return "size[10,8]"..
|
||||||
|
default.gui_bg..
|
||||||
|
default.gui_bg_img..
|
||||||
|
default.gui_slots..
|
||||||
|
"tabheader[0,0;tab;init,loop,outp,notes,help;1;;true]"..
|
||||||
|
"textarea[0.3,0.2;10,8.3;init;function init();"..init.."]"..
|
||||||
|
"label[0,7.3;end]"..
|
||||||
|
"button_exit[4.4,7.5;1.8,1;cancel;Cancel]"..
|
||||||
|
"button[6.3,7.5;1.8,1;save;Save]"..
|
||||||
|
"button[8.2,7.5;1.8,1;"..cmnd.."]"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formspec2(meta)
|
||||||
|
local running = meta:get_int("state") == tubelib.RUNNING
|
||||||
|
local cmnd = running and "stop;Stop" or "start;Start"
|
||||||
|
local loop = meta:get_string("loop")
|
||||||
|
loop = minetest.formspec_escape(loop)
|
||||||
|
return "size[10,8]"..
|
||||||
|
default.gui_bg..
|
||||||
|
default.gui_bg_img..
|
||||||
|
default.gui_slots..
|
||||||
|
"tabheader[0,0;tab;init,loop,outp,notes,help;2;;true]"..
|
||||||
|
"textarea[0.3,0.2;10,8.3;loop;function loop(ticks, elapsed);"..loop.."]"..
|
||||||
|
"label[0,7.3;end]"..
|
||||||
|
"button_exit[4.4,7.5;1.8,1;cancel;Cancel]"..
|
||||||
|
"button[6.3,7.5;1.8,1;save;Save]"..
|
||||||
|
"button[8.2,7.5;1.8,1;"..cmnd.."]"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formspec3(meta)
|
||||||
|
local running = meta:get_int("state") == tubelib.RUNNING
|
||||||
|
local cmnd = running and "stop;Stop" or "start;Start"
|
||||||
|
local output = meta:get_string("output")
|
||||||
|
output = minetest.formspec_escape(output)
|
||||||
|
return "size[10,8]"..
|
||||||
|
default.gui_bg..
|
||||||
|
default.gui_bg_img..
|
||||||
|
default.gui_slots..
|
||||||
|
"tabheader[0,0;tab;init,loop,outp,notes,help;3;;true]"..
|
||||||
|
"textarea[0.3,0.2;10,8.3;help;Output:;"..output.."]"..
|
||||||
|
"button[4.4,7.5;1.8,1;clear;Clear]"..
|
||||||
|
"button[6.3,7.5;1.8,1;update;Update]"..
|
||||||
|
"button[8.2,7.5;1.8,1;"..cmnd.."]"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formspec4(meta)
|
||||||
|
local notes = meta:get_string("notes")
|
||||||
|
notes = minetest.formspec_escape(notes)
|
||||||
|
return "size[10,8]"..
|
||||||
|
default.gui_bg..
|
||||||
|
default.gui_bg_img..
|
||||||
|
default.gui_slots..
|
||||||
|
"tabheader[0,0;tab;init,loop,outp,notes,help;4;;true]"..
|
||||||
|
"textarea[0.3,0.2;10,8.3;notes;Notepad:;"..notes.."]"..
|
||||||
|
"button_exit[6.3,7.5;1.8,1;cancel;Cancel]"..
|
||||||
|
"button[8.2,7.5;1.8,1;save;Save]"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formspec5(items, pos, text)
|
||||||
|
text = minetest.formspec_escape(text)
|
||||||
|
return "size[10,8]"..
|
||||||
|
default.gui_bg..
|
||||||
|
default.gui_bg_img..
|
||||||
|
default.gui_slots..
|
||||||
|
"tabheader[0,0;tab;init,loop,outp,notes,help;5;;true]"..
|
||||||
|
"label[0,-0.2;Functions:]"..
|
||||||
|
"dropdown[0.3,0.2;10,8.3;functions;"..items..";"..pos.."]"..
|
||||||
|
"textarea[0.3,1.3;10,8;help;Help:;"..text.."]"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function error(pos, err)
|
||||||
|
output(pos, err)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local number = meta:get_string("number")
|
||||||
|
meta:set_string("infotext", "Controller "..number..": error")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compile(pos, meta, number)
|
||||||
|
local init = meta:get_string("init")
|
||||||
|
local loop = meta:get_string("loop")
|
||||||
|
local owner = meta:get_string("owner")
|
||||||
|
local env = table.copy(tCommands)
|
||||||
|
env.meta = {pos=pos, owner=owner, number=number}
|
||||||
|
local code = safer_lua.init(pos, init, loop, env, error)
|
||||||
|
|
||||||
|
if code then
|
||||||
|
Cache[number] = {code=code, inputs={}}
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_timer(pos, elapsed)
|
||||||
|
local t = minetest.get_us_time()
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local number = meta:get_string("number")
|
||||||
|
if Cache[number] or compile(pos, meta, number) then
|
||||||
|
|
||||||
|
local code = Cache[number].code
|
||||||
|
local res = safer_lua.run_loop(pos, elapsed, code, error)
|
||||||
|
|
||||||
|
t = minetest.get_us_time() - t
|
||||||
|
print("time", t)
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function start_controller(pos)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local number = meta:get_string("number")
|
||||||
|
if compile(pos, meta, number) then
|
||||||
|
meta:set_int("state", tubelib.RUNNING)
|
||||||
|
minetest.get_node_timer(pos):start(1)
|
||||||
|
meta:set_string("infotext", "Controller "..number..": running")
|
||||||
|
end
|
||||||
|
meta:set_string("output", "<press update>")
|
||||||
|
meta:set_string("formspec", formspec3(meta))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stop_controller(pos)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local number = meta:get_string("number")
|
||||||
|
meta:set_int("state", tubelib.STOPPED)
|
||||||
|
minetest.get_node_timer(pos):stop()
|
||||||
|
meta:set_string("infotext", "Controller "..number..": stopped")
|
||||||
|
meta:set_string("formspec", formspec2(meta))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_receive_fields(pos, formname, fields, player)
|
||||||
|
if minetest.is_protected(pos, player:get_player_name()) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
|
||||||
|
--print(dump(fields))
|
||||||
|
if fields.cancel == nil then
|
||||||
|
if fields.init then
|
||||||
|
meta:set_string("init", fields.init)
|
||||||
|
meta:set_string("formspec", formspec1(meta))
|
||||||
|
elseif fields.loop then
|
||||||
|
meta:set_string("loop", fields.loop)
|
||||||
|
meta:set_string("formspec", formspec2(meta))
|
||||||
|
elseif fields.notes then
|
||||||
|
meta:set_string("notes", fields.notes)
|
||||||
|
meta:set_string("formspec", formspec4(meta))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fields.update then
|
||||||
|
meta:set_string("formspec", formspec3(meta))
|
||||||
|
elseif fields.clear then
|
||||||
|
meta:set_string("output", "<press update>")
|
||||||
|
meta:set_string("formspec", formspec3(meta))
|
||||||
|
elseif fields.tab == "1" then
|
||||||
|
meta:set_string("formspec", formspec1(meta))
|
||||||
|
elseif fields.tab == "2" then
|
||||||
|
meta:set_string("formspec", formspec2(meta))
|
||||||
|
elseif fields.tab == "3" then
|
||||||
|
meta:set_string("formspec", formspec3(meta))
|
||||||
|
elseif fields.tab == "4" then
|
||||||
|
meta:set_string("formspec", formspec4(meta))
|
||||||
|
elseif fields.tab == "5" then
|
||||||
|
meta:set_string("formspec", formspec5(sFunctionList, 1, sHELP))
|
||||||
|
elseif fields.start == "Start" then
|
||||||
|
start_controller(pos)
|
||||||
|
elseif fields.stop == "Stop" then
|
||||||
|
stop_controller(pos)
|
||||||
|
elseif fields.functions then
|
||||||
|
local key = fields.functions
|
||||||
|
local text = tHelpTexts[key] or ""
|
||||||
|
local pos = tFunctionIndex[key] or 1
|
||||||
|
meta:set_string("formspec", formspec5(sFunctionList, pos, text))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_node("sl_controller:controller", {
|
||||||
|
description = "SaferLUA Controller",
|
||||||
|
inventory_image = "sl_controller_inventory.png",
|
||||||
|
wield_image = "sl_controller_inventory.png",
|
||||||
|
stack_max = 1,
|
||||||
|
tiles = {
|
||||||
|
-- up, down, right, left, back, front
|
||||||
|
"smartline.png",
|
||||||
|
"smartline.png",
|
||||||
|
"smartline.png",
|
||||||
|
"smartline.png",
|
||||||
|
"smartline.png",
|
||||||
|
"smartline.png^sl_controller.png",
|
||||||
|
},
|
||||||
|
|
||||||
|
drawtype = "nodebox",
|
||||||
|
node_box = {
|
||||||
|
type = "fixed",
|
||||||
|
fixed = {
|
||||||
|
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
after_place_node = function(pos, placer)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local number = tubelib.add_node(pos, "sl_controller:controller")
|
||||||
|
meta:set_string("owner", placer:get_player_name())
|
||||||
|
--print("after_place_node", number)
|
||||||
|
meta:set_string("number", number)
|
||||||
|
meta:set_int("state", tubelib.STOPPED)
|
||||||
|
meta:set_string("init", "-- called only once")
|
||||||
|
meta:set_string("loop", "-- called every second")
|
||||||
|
meta:set_string("notes", "For your notes / snippets")
|
||||||
|
meta:set_string("formspec", formspec1(meta))
|
||||||
|
meta:set_string("infotext", "Controller "..number..": stopped")
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_receive_fields = on_receive_fields,
|
||||||
|
|
||||||
|
on_dig = function(pos, node, puncher, pointed_thing)
|
||||||
|
if minetest.is_protected(pos, puncher:get_player_name()) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.node_dig(pos, node, puncher, pointed_thing)
|
||||||
|
tubelib.remove_node(pos)
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_timer = on_timer,
|
||||||
|
|
||||||
|
paramtype = "light",
|
||||||
|
sunlight_propagates = true,
|
||||||
|
paramtype2 = "facedir",
|
||||||
|
groups = {choppy=1, cracky=1, crumbly=1},
|
||||||
|
is_ground_content = false,
|
||||||
|
sounds = default.node_sound_stone_defaults(),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
minetest.register_craft({
|
||||||
|
type = "shapeless",
|
||||||
|
output = "sl_controller:controller",
|
||||||
|
recipe = {"smartline:controller"}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
local function set_input(number, input, val)
|
||||||
|
if input then
|
||||||
|
if Cache[number] and Cache[number].inputs then
|
||||||
|
--print("set_input", input, val)
|
||||||
|
Cache[number].inputs[input] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function sl_controller.get_input(number, input)
|
||||||
|
--print("get_input", number, input, dump(Cache[number].inputs))
|
||||||
|
if input then
|
||||||
|
if Cache[number] and Cache[number].inputs then
|
||||||
|
return Cache[number].inputs[input] or "off"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return "off"
|
||||||
|
end
|
||||||
|
|
||||||
|
tubelib.register_node("sl_controller:controller", {}, {
|
||||||
|
on_recv_message = function(pos, topic, payload)
|
||||||
|
-----------------------------------------------------
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local number = meta:get_string("number")
|
||||||
|
---------------------------------------------
|
||||||
|
if topic == "on" then
|
||||||
|
set_input(number, payload, topic)
|
||||||
|
elseif topic == "off" then
|
||||||
|
set_input(number, payload, topic)
|
||||||
|
elseif topic == "state" then
|
||||||
|
local state = meta:get_int("state")
|
||||||
|
return tubelib.statestring(state)
|
||||||
|
else
|
||||||
|
return "unsupported"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
5
sl_controller/depends.txt
Normal file
5
sl_controller/depends.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
default
|
||||||
|
doors
|
||||||
|
tubelib
|
||||||
|
safer_lua
|
||||||
|
|
2
sl_controller/description.txt
Normal file
2
sl_controller/description.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
tbd.
|
||||||
|
|
14
sl_controller/init.lua
Normal file
14
sl_controller/init.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
sl_controller
|
||||||
|
=============
|
||||||
|
|
||||||
|
Copyright (C) 2018 Joachim Stolberg
|
||||||
|
|
||||||
|
LGPLv2.1+
|
||||||
|
See LICENSE.txt for more information
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
dofile(minetest.get_modpath("sl_controller") .. "/controller.lua")
|
||||||
|
dofile(minetest.get_modpath("sl_controller") .. "/commands.lua")
|
1
sl_controller/mod.conf
Normal file
1
sl_controller/mod.conf
Normal file
@ -0,0 +1 @@
|
|||||||
|
name=sl_controller
|
BIN
sl_controller/textures/sl_controller.png
Normal file
BIN
sl_controller/textures/sl_controller.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 546 B |
BIN
sl_controller/textures/sl_controller_inventory.png
Normal file
BIN
sl_controller/textures/sl_controller_inventory.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 838 B |
Loading…
Reference in New Issue
Block a user