Compare commits

...

2 Commits

  1. BIN
      src/drivers/nodemcu/lua/lfs.img
  2. BIN
      src/drivers/nodemcu/lua/lfs.zip
  3. 50
      src/drivers/nodemcu/lua/lfs/_init.lua
  4. 27
      src/drivers/nodemcu/lua/lfs/dummy_strings.lua
  5. 377
      src/drivers/nodemcu/lua/lfs/wiot.lua
  6. 87
      src/drivers/nodemcu/lua/lfs_original/_init.lua
  7. 37
      src/drivers/nodemcu/lua/lfs_original/dummy_strings.lua
  8. 488
      src/drivers/nodemcu/lua/lfs_original/wiot.lua
  9. BIN
      src/drivers/nodemcu/lua/wiot.lc

Binary file not shown.

Binary file not shown.

@ -1,26 +1,3 @@
--
-- File: _init.lua
--[[
This is a template for the LFS equivalent of the SPIFFS init.lua.
It is a good idea to such an _init.lua module to your LFS and do most of the LFS
module related initialisaion in this. This example uses standard Lua features to
simplify the LFS API.
For Lua 5.1, the first section adds a 'LFS' table to _G and uses the __index
metamethod to resolve functions in the LFS, so you can execute the main
function of module 'fred' by executing LFS.fred(params), etc.
It also implements some standard readonly properties:
LFS._time The Unix Timestamp when the luac.cross was executed. This can be
used as a version identifier.
LFS._config This returns a table of useful configuration parameters, hence
print (("0x%6x"):format(LFS._config.lfs_base))
gives you the parameter to use in the luac.cross -a option.
LFS._list This returns a table of the LFS modules, hence
print(table.concat(LFS._list,'\n'))
gives you a single column listing of all modules in the LFS.
For Lua 5.3 LFS table is populated by the LFS implementation in C so this part
of the code is skipped.
---------------------------------------------------------------------------------]]
local lfsindex = node.LFS and node.LFS.get or node.flashindex
local G=_ENV or getfenv()
local lfs_t
@ -42,39 +19,20 @@ if _VERSION == 'Lua 5.1' then
return nil
end
end,
__newindex = function(_, name, value) -- luacheck: no unused
__newindex = function(_, name, value)
error("LFS is readonly. Invalid write to LFS." .. name, 2)
end,
}
setmetatable(lfs_t,lfs_t)
G.module = nil -- disable Lua 5.0 style modules to save RAM
G.module = nil
package.seeall = nil
else
lfs_t = node.LFS
end
G.LFS = lfs_t
--[[-------------------------------------------------------------------------------
The second section adds the LFS to the require searchlist, so that you can
require a Lua module 'jean' in the LFS by simply doing require "jean". However
note that this is at the search entry following the FS searcher, so if you also
have jean.lc or jean.lua in SPIFFS, then this SPIFFS version will get loaded into
RAM instead of using. (Useful, for development).
See docs/en/lfs.md and the 'loaders' array in app/lua/loadlib.c for more details.
---------------------------------------------------------------------------------]]
package.loaders[3] = function(module) -- loader_flash
package.loaders[3] = function(module)
return lfs_t[module]
end
--[[----------------------------------------------------------------------------
These replace the builtins loadfile & dofile with ones which preferentially
load from the filesystem and fall back to LFS. Flipping the search order
is an exercise left to the reader.-
------------------------------------------------------------------------------]]
local lf = loadfile
G.loadfile = function(n)
if file.exists(n) then return lf(n) end
@ -82,6 +40,4 @@ G.loadfile = function(n)
local fn = mod and lfsindex(mod)
return (fn or error (("Cannot find '%s' in FS or LFS"):format(n))) and fn
end
-- Lua's dofile (luaB_dofile) reaches directly for luaL_loadfile; shim instead
G.dofile = function(n) return assert(loadfile(n))() end

@ -1,28 +1,3 @@
--
-- File: LFS_dummy_strings.lua
--[[
luac.cross -f generates a ROM string table which is part of the compiled LFS
image. This table includes all strings referenced in the loaded modules.
If you want to preload other string constants, then one way to achieve this is
to include a dummy module in the LFS that references the strings that you want
to load. You never need to call this module; it's inclusion in the LFS image is
enough to add the strings to the ROM table. Your application can use any strings
in the ROM table without incuring any RAM or Lua Garbage Collector (LGC)
overhead.
The local preload example is a useful starting point. However, if you call the
following code in your application during testing, then this will provide a
listing of the current RAM string table.
do
local a=debug.getstrings'RAM'
for i =1, #a do a[i] = ('%q'):format(a[i]) end
print ('local preload='..table.concat(a,','))
end
This will exclude any strings already in the ROM table, so the output is the list
of putative strings that you should consider adding to LFS ROM table.
---------------------------------------------------------------------------------
]]--
-- luacheck: ignore
local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"_LOADLIB", "__add", "__call", "__concat", "__div", "__eq", "__gc", "__index",
"__le", "__len", "__lt", "__mod", "__mode", "__mul", "__newindex", "__pow",
@ -31,7 +6,7 @@ local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"loader", "loaders", "loadlib", "module", "net.tcpserver", "net.tcpsocket",
"net.udpsocket", "newproxy", "package", "pairs", "path", "preload", "reload",
"require", "seeall", "wdclr", "not enough memory", "sjson.decoder","sjson.encoder",
"tmr.timer", "table", "string", "number", "function", "director", "collect", "__getInfo", "__setNS",
"tmr.timer", "table", "string", "number", "function", "director", "collect", "__getInfo", "__setNS", "__checkNS",
"__setFunc", "__restart", "default", "from", "to", "body", "name", "online", "func", "nid",
"connection", "disconnection", "receive", "set", "get", "del", "list", "clear",
'config.json', '__system/flag', 'func.json', '__data/', '__system/swap/'

@ -1,10 +1,6 @@
print('total', node.heap());
main = coroutine.create(function(__run)
-- WIOT Lua Firmware on NodeMCU
__main = coroutine.create(function(__run)
--Packages Used: file, sjson, encoder, timer, node, wifi, net, gpio, uart
collectgarbage("collect")
print('close', node.heap())
--Global Constant
-----------------
@ -13,42 +9,51 @@ print('close', node.heap())
local FUNC_PATH = 'func.json';
local DB_PREFIX = '__data/';
local SWAP_PREFIX = '__system/swap/';
local SIGNAL_LED = 0;
collectgarbage("collect")
print('Global Constant', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('Global Constant', node.heap())
--Global Methods (in RAM)
----------------
-- Package Methods
local pack = {
encode = function(obj)
--encode any type other than function into string
encode = function(obj) --(any data) => string packedString
local status, json = pcall(sjson.encode, {obj});
if status then
--if encode is success
return string.sub(json, 2, -2);
else
--if the encode is fail
return '';
end
end,
decode = function(str)
--decode the string into the original type data
decode = function(str) --(string packedString) => any data
if type(str) ~= 'string' then
return nil
end;
local status, obj = pcall(sjson.decode, '['..str..']');
if status then
--if decode is success
return obj[1];
else
--if decode is fail
return nil;
end
end
}
-- File Object Operation
local fs = {
read = function(f)--f:filename
return pack.decode(file.getcontents(f));
--read a multi-type data from a file
read = function(f) --(string filename) => any filedata
return pack.decode(file.getcontents(f));
end,
write = function(f, obj)
--write any type data other than function into a file
write = function(f, obj) --(string filename, any data) => bool status
local res = pack.encode(obj);
if res == '' then
return false;
@ -61,7 +66,9 @@ print('Global Constant', node.heap())
end
}
--split string
local stringSplit = function(str, reps)
--split the string with reps and return a table with
--all elemets. e.g. ('abcdabcd', 'a') => {'bcd', 'bcd'}
local stringSplit = function(str, reps) --(string string, string separator) => table splitedString
local resultStrList = {}
string.gsub(str,'[^'..reps..']+', function (w)
table.insert(resultStrList,w);
@ -69,17 +76,53 @@ print('Global Constant', node.heap())
return resultStrList;
end
collectgarbage("collect")
print('Global Methods (in RAM)', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('Global Methods (in RAM)', node.heap())
-- Signal LED Begin
-------------------
--Mode Init
gpio.mode(SIGNAL_LED, gpio.OUTPUT);
local signal_timer = tmr.create();
--Control Methods
local setSignalInterval = function(interval) --(number interval) => nil
signal_timer:alarm(interval, tmr.ALARM_AUTO, function()
--reverse the SIGNAL led output, 0->1, 1->0
if gpio.read(SIGNAL_LED) == gpio.HIGH then
gpio.write(SIGNAL_LED, gpio.LOW);
else
gpio.write(SIGNAL_LED, gpio.HIGH);
end
end);
end
--SIGNAL: Start
setSignalInterval(200);
--For Debug Purpose
--collectgarbage("collect")
--print('Signal LED Begin', node.heap())
--Load Config (Assume config file is
-- exist and good)
-------------
--load config
local config = fs.read(CONFIG_PATH);
collectgarbage("collect")
print('Load Config', node.heap())
--Load Func to RAM
local func = fs.read(FUNC_PATH);
--Default Mode
if type(func) ~= 'table' or type(func.id) ~= 'string' or type(func.online) ~= 'string' then
func = {
id = 'default',
online = ''
}
end
--For Debug Purpose
--collectgarbage("collect")
--print('Load Config', node.heap())
--Check Flag (Assume flag is at FLAG_PATH in SPIFF)
--(flag represent the number of startup failures before)
@ -100,8 +143,11 @@ print('Load Config', node.heap())
end
--Release Resource
flag = nil;
collectgarbage("collect")
print('Check Flag', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('Check Flag', node.heap())
--WiFi Setup
------------
@ -113,121 +159,127 @@ print('Check Flag', node.heap())
wifi.sta.config(config.wifi);
--Connect to AP
tmr.create():alarm(1000, tmr.ALARM_AUTO, function(timer)
print('Setting WiFi');
--print('Setting WiFi');
if wifi.sta.getip() ~= nil then --When WiFi is Ready
print('WiFi OK');
--print('WiFi OK');
--Release Timer Resources
timer:unregister();
--Resume Main Process
coroutine.resume(main);
coroutine.resume(__main);
end
end);
--Suspend Main Process Until WiFi is ready
coroutine.yield();
collectgarbage("collect")
print('WiFi Setup', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('WiFi Setup', node.heap())
--SIGNAL: WiFi OK
setSignalInterval(600);
--SWAP Setup
--SWAP Setup (SWAP is a part of space in FLASH
-- specifically storage some low-frequency
-- usage data and methods)
--------------
-- SWAP Method
local swap = {}
function swap:set(key, val, prefix)
function swap:set(key, val, prefix) --(string key, any data, string prefix) => bool status
return fs.write(prefix..encoder.toBase64(key), val);
end
function swap:get(key, prefix)
function swap:get(key, prefix) --(string key, string prefix) => any data
return fs.read(prefix..encoder.toBase64(key));
end
function swap:del(key, prefix)
function swap:del(key, prefix) --(string key, string prefix) => nil
file.remove(prefix..encoder.toBase64(key));
end
function swap:list(prefix)
--list a table of all keys with the prefix
function swap:list(prefix) --(string prefix) => table ['key']
local fileList = file.list(prefix..'.');
local keys = {};
for k, v in pairs(fileList) do print('ss', k)
for k, v in pairs(fileList) do
--push key to keys table
table.insert(keys, encoder.fromBase64(string.sub(k, #prefix + 1)));
end
return keys;
end
function swap:clear(prefix)print('s', prefix)
for index, key in pairs(self:list(prefix)) do print('sss', key)
--delete all key-value with the prefix
function swap:clear(prefix) --(string prefix) => nil
for index, key in pairs(self:list(prefix)) do
self:del(key, prefix);
end
end
collectgarbage("collect")
print('SWAP Setup', node.heap())
--DB Setup
--------------
--DB Methods
local db = {
set = function(key, val)
return swap:set(key, val, DB_PREFIX);
end,
get = function(key)
return swap:get(key, DB_PREFIX);
end,
del = function(key, prefix)
swap:del(key, DB_PREFIX);
end,
list = function(prefix)
return swap:list(DB_PREFIX);
end,
clear = function()
swap:clear(DB_PREFIX);
end
}
collectgarbage("collect")
print('DB Setup', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('SWAP Setup', node.heap())
--Name Service (NS) Setup
--Name Service (NS) Setup (NS service provide Methods
-- to convert nid into UDP ip and port
-- so the direct connection to the corresponding
-- node can be established )
--------------------
local ns = setmetatable({}, {
--read method
__index = function(table, key)
--search key in SWAP
local res = swap:get(key, SWAP_PREFIX..'NS/');
if res == nil or res.port == nil or res.ip == nil then
--if no such key or wrong format, return nil
return nil;
else
--if good, return the ns table
return res;
end
end,
--assign method
__newindex = function(table, key, val)
--storage the ns key-value into SWAP
swap:set(key, val, SWAP_PREFIX..'NS/');
end
});
collectgarbage("collect")
print('NS Setup', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('NS Setup', node.heap())
--MSG Register Init
--MSG Register Init ( MSG register storages the user-defined
-- methods to handle the income message
-- from inter-board and server-client communication)
-------------------
--MSG Register Clear
swap:clear(SWAP_PREFIX..'M/');print('a')
swap:clear(SWAP_PREFIX..'M/');
--MSG register (in SWAP)
local msgReg = setmetatable({}, {
--read method
__index = function(table, key)
--search key in SWAP
local res = swap:get(key, SWAP_PREFIX..'M/');
if res == nil then
--if no such key, return nil
return nil;
else
--if has such key, return the method
return loadstring(encoder.fromBase64(res));
end
end,
--assign method
__newindex = function(table, key, val)
if type(val) ~= 'function' then
--if value is not a function, then delete the corresponding key in SWAP
swap:del(key, SWAP_PREFIX..'M/');
else
--if value is a function, then save it in SWAP
swap:set(key, encoder.toBase64(string.dump(val)), SWAP_PREFIX..'M/');
end
end
});print('b')
});
--MSG Reg Method
msgReg_run = function(data)
msgReg_run = function(data) --(string incomingData) => nil
--decode data
local data = pack.decode(data);
--check data
@ -241,96 +293,176 @@ print('NS Setup', node.heap())
end
end
collectgarbage("collect")
print('MSG Register Init', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('MSG Register Init', node.heap())
--UDP Startup
--UDP Startup ( Start up a udp server
-- for inter-board communication)
-------------
--Start UDP Service
--create a udp server
local udpd = net.createUDPSocket();
udpd:listen(config.msg.port);
--when message comming
udpd:on('receive', function(socket, data, port, ip)
--send message to msg register
msgReg_run(data);
end);
collectgarbage("collect")
print('UDP Startup', node.heap())
--TCP Startup
--For Debug Purpose
--collectgarbage("collect")
--print('UDP Startup', node.heap())
--TCP Startup ( Start up a tcp client
-- to communication with
-- the director )
-------------
--Start TCP Service
local tcpd = net.createConnection(net.TCP, 0);
--Set the socket variable to nil as a signal of disconnect
local socket = nil;
--when tcp is connected
tcpd:on('connection', function(sck, data)
--set socket variable to alive socket object
socket = sck;
--SIGNAL: TCP OK, release timer object
signal_timer:unregister();
if func.id == 'default' then
--if no user Func, signal led on
gpio.write(SIGNAL_LED, gpio.LOW);
else
--if runs user Func, signal led off
gpio.write(SIGNAL_LED, gpio.HIGH);
end
end);
--when tcp is disconnect or a connect try timeout
tcpd:on('disconnection', function(sck, data)
--set socket variable to nil as a signal of disconnect
socket = nil;
--reconnect after 1s
tmr.create():alarm(1000, tmr.ALARM_SINGLE, function()
tcpd:connect(config.director.port, config.director.ip);
end)
--SIGNAL: TCP BAD
setSignalInterval(1000);
end);
--when tcp incomming message
tcpd:on('receive', function(sck, data)
--send message to msg register
msgReg_run(data);
end);
--connect to director
tcpd:connect(config.director.port, config.director.ip);
tmr.create():alarm(10000, tmr.ALARM_AUTO, function()
if socket == nil then
tcpd:connect(config.director.port, config.director.ip);
end
--connect to director with 1000 delay
tmr.create():alarm(1000, tmr.ALARM_SINGLE, function()
tcpd:connect(config.director.port, config.director.ip);
end)
collectgarbage("collect")
print('TCP Startup', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('TCP Startup', node.heap())
--MSG Setup
--MSG Setup ( MSG module is exposed to user
-- program runtime environment.
-- It provide inter-board communication
-- APIs that user' program can easily
-- use)
--------------
-- MSG Methods
local msg = {
send = function(to, name, body, proxy)
--query target nid in ns
local port_ip = ns[to];
--pack the msg body
local package = pack.encode({
from = config.nid,
to = to,
name = name,
body = body
});
--If no ns record or the proxy params is true,
--send via tcp to director
if port_ip == nil or proxy == true then
if socket ~= nil then
--if tcp is alive
socket:send(package);
return ture;
return true;
else
--if tcp is dead
return false;
end
end
--if have ns record, send via udp to target device directly
udpd:send(port_ip.port, port_ip.ip, package);
return true;
end,
onSend = function(name, method)
--register a new method to msg register
msgReg[name] = method;
end
}
collectgarbage("collect")
print('MSG Setup', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('MSG Setup', node.heap())
--DB Setup (DB Module is exposed to user program
-- runtimg environment. It provide a mini
-- local key-value database basing on Flash
-- file system)
--------------
--DB Methods
local db = {
set = function(key, val) ---(string key, any data) => bool status
return swap:set(key, val, DB_PREFIX);
end,
get = function(key) --(string key) => any data
return swap:get(key, DB_PREFIX);
end,
del = function(key) --(string key) => nil
swap:del(key, DB_PREFIX);
end,
list = function() --() => table ['key']
return swap:list(DB_PREFIX);
end,
clear = function() -- () => nil
swap:clear(DB_PREFIX);
end
}
--For Debug Purpose
--collectgarbage("collect")
--print('DB Setup', node.heap())
--Heartbeat Startup
--Heartbeat Startup (Heartbeat Service keep the TCP channel
-- to the director alive to against NAT
-- timeout.)
-------------------
local tcpd_heartbeat_timer = tmr.create():alarm(config.director.HeartbeatInterval, tmr.ALARM_AUTO, function()
if socket then
socket:send(config.nid..':'..tostring(tmr.time()));
end
end);
collectgarbage("collect")
print('Heartbeat Startup', node.heap())
--System APIs
--For Debug Purpose
--collectgarbage("collect")
--print('Heartbeat Startup', node.heap())
--System APIs (System APIs provide a set of system-level APIs
-- for the director so it can operate this device)
-------------
--getInfo API
rawset(msgReg, '__getInfo', function(from, body)
if from == 'director' then
--Send info package to director
msg.send('director', '__getInfo', {
remainHeap = node.heap(),
remainFS = file.fsinfo(),
remainHeap = node.heap(), --remain RAM
remainFS = file.fsinfo(), --remain Flash storage
msgPort = config.msg.port,
HeartbeatInterval = config.director.HeartbeatInterval,
ns = swap:list(SWAP_PREFIX..'NS/'),
@ -342,26 +474,43 @@ print('Heartbeat Startup', node.heap())
rawset(msgReg, '__setNS', function(from, body)
if from == 'director' and type(body) == 'table' and type(body.nid) == 'string' then
if type(body.port) == 'number' and type(body.ip) == 'string' then
--if request body is legal, update local ns
ns[body.nid] = {
port = body.port,
ip = body.ip
}
else
--if request body is legal but no content, del corresponding local ns
ns[body.nid] = nil;
end
end
end);
--checkNS API
rawset(msgReg, '__checkNS', function(from, body)
if type(body) == 'string' then
if from == 'director' then
--if query from director, send to peer via udp
msg.send(body, '__checkNS', config.nid);
else
--if query from peer, send to director via tcp
msg.send('director', '__checkNS', body, true);
end
end
end);
--setFunc API
rawset(msgReg, '__setFunc', function(from, body)
if from == 'director' then
if type(body.func) == 'table' and type(body.func.id) == 'string' and type(body.func.online) == 'string' then
--if the request body is legal, update local FUNC
fs.write(FUNC_PATH, body.func);
--if have ns in request body, then update local ns list
if type(body.ns) == 'table' then
swap:clear(SWAP_PREFIX..'NS/');
for k, v in pairs(body.ns) do
ns[k] = v;
end
end
--restart the system to run new Func
node.restart();
end
end
@ -373,52 +522,54 @@ print('Heartbeat Startup', node.heap())
end
end
collectgarbage("collect")
print('System APIs', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('System APIs', node.heap())
--FUNC Startup
--FUNC Startup (Run the user's program
-- in sandbox)
--------------
--Load Func to RAM
local func = fs.read(FUNC_PATH);
--Default Mode
if type(func) ~= 'table' or type(func.id) ~= 'string' or type(func.online) ~= 'string' then
func = {
id = 'default',
online = ''
}
end
--warp running
print(func.id)
local status, errmsg = __run(func.online, db, msg);
print(status, errmsg)
--print(status, errmsg)
if status then
--flag watchdog, handle unknown error
tmr.create():alarm(10000, tmr.ALARM_SINGLE, function()
--the startup of user program is successful
file.remove(FLAG_PATH);
end);
else
--user program startup fail, restart system
node.restart();
end
--release resources
status, errmsg = nil, nil;
collectgarbage("collect")
print('FUNC Startup', node.heap())
--For Debug Purpose
--collectgarbage("collect")
--print('FUNC Startup', node.heap())
end);
--SIGNAL: FUNC OK
setSignalInterval(1000);
end);
coroutine.resume(main, function(func, db, msg)
--Boot __main process with a sandbox for user program running
coroutine.resume(__main, function(func, db, msg) --This is a sandbox env for user's program
-- (function userProgram, module db, module msg) => bool status, string errMessage
--handle string to code error
local status, res = pcall(loadstring, 'return function(db, msg) '..func..' end');
if not status then
return status, res;
else
--handle syntax error
status, res = pcall(res);
if not status then
return status, res;
else
--handle permission error
return pcall(res, db, msg);
end
end

@ -0,0 +1,87 @@
--
-- File: _init.lua
--[[
This is a template for the LFS equivalent of the SPIFFS init.lua.
It is a good idea to such an _init.lua module to your LFS and do most of the LFS
module related initialisaion in this. This example uses standard Lua features to
simplify the LFS API.
For Lua 5.1, the first section adds a 'LFS' table to _G and uses the __index
metamethod to resolve functions in the LFS, so you can execute the main
function of module 'fred' by executing LFS.fred(params), etc.
It also implements some standard readonly properties:
LFS._time The Unix Timestamp when the luac.cross was executed. This can be
used as a version identifier.
LFS._config This returns a table of useful configuration parameters, hence
print (("0x%6x"):format(LFS._config.lfs_base))
gives you the parameter to use in the luac.cross -a option.
LFS._list This returns a table of the LFS modules, hence
print(table.concat(LFS._list,'\n'))
gives you a single column listing of all modules in the LFS.
For Lua 5.3 LFS table is populated by the LFS implementation in C so this part
of the code is skipped.
---------------------------------------------------------------------------------]]
local lfsindex = node.LFS and node.LFS.get or node.flashindex
local G=_ENV or getfenv()
local lfs_t
if _VERSION == 'Lua 5.1' then
lfs_t = {
__index = function(_, name)
local fn_ut, ba, ma, size, modules = lfsindex(name)
if not ba then
return fn_ut
elseif name == '_time' then
return fn_ut
elseif name == '_config' then
local fs_ma, fs_size = file.fscfg()
return {lfs_base = ba, lfs_mapped = ma, lfs_size = size,
fs_mapped = fs_ma, fs_size = fs_size}
elseif name == '_list' then
return modules
else
return nil
end
end,
__newindex = function(_, name, value) -- luacheck: no unused
error("LFS is readonly. Invalid write to LFS." .. name, 2)
end,
}
setmetatable(lfs_t,lfs_t)
G.module = nil -- disable Lua 5.0 style modules to save RAM
package.seeall = nil
else
lfs_t = node.LFS
end
G.LFS = lfs_t
--[[-------------------------------------------------------------------------------
The second section adds the LFS to the require searchlist, so that you can
require a Lua module 'jean' in the LFS by simply doing require "jean". However
note that this is at the search entry following the FS searcher, so if you also
have jean.lc or jean.lua in SPIFFS, then this SPIFFS version will get loaded into
RAM instead of using. (Useful, for development).
See docs/en/lfs.md and the 'loaders' array in app/lua/loadlib.c for more details.
---------------------------------------------------------------------------------]]
package.loaders[3] = function(module) -- loader_flash
return lfs_t[module]
end
--[[----------------------------------------------------------------------------
These replace the builtins loadfile & dofile with ones which preferentially
load from the filesystem and fall back to LFS. Flipping the search order
is an exercise left to the reader.-
------------------------------------------------------------------------------]]
local lf = loadfile
G.loadfile = function(n)
if file.exists(n) then return lf(n) end
local mod = n:match("(.*)%.l[uc]a?$")
local fn = mod and lfsindex(mod)
return (fn or error (("Cannot find '%s' in FS or LFS"):format(n))) and fn
end
-- Lua's dofile (luaB_dofile) reaches directly for luaL_loadfile; shim instead
G.dofile = function(n) return assert(loadfile(n))() end

@ -0,0 +1,37 @@
--
-- File: LFS_dummy_strings.lua
--[[
luac.cross -f generates a ROM string table which is part of the compiled LFS
image. This table includes all strings referenced in the loaded modules.
If you want to preload other string constants, then one way to achieve this is
to include a dummy module in the LFS that references the strings that you want
to load. You never need to call this module; it's inclusion in the LFS image is
enough to add the strings to the ROM table. Your application can use any strings
in the ROM table without incuring any RAM or Lua Garbage Collector (LGC)
overhead.
The local preload example is a useful starting point. However, if you call the
following code in your application during testing, then this will provide a
listing of the current RAM string table.
do
local a=debug.getstrings'RAM'
for i =1, #a do a[i] = ('%q'):format(a[i]) end
print ('local preload='..table.concat(a,','))
end
This will exclude any strings already in the ROM table, so the output is the list
of putative strings that you should consider adding to LFS ROM table.
---------------------------------------------------------------------------------
]]--
-- luacheck: ignore
local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"_LOADLIB", "__add", "__call", "__concat", "__div", "__eq", "__gc", "__index",
"__le", "__len", "__lt", "__mod", "__mode", "__mul", "__newindex", "__pow",
"__sub", "__tostring", "__unm", "collectgarbage", "cpath", "debug", "file",
"file.obj", "file.vol", "flash", "getstrings", "index", "ipairs", "list", "loaded",
"loader", "loaders", "loadlib", "module", "net.tcpserver", "net.tcpsocket",
"net.udpsocket", "newproxy", "package", "pairs", "path", "preload", "reload",
"require", "seeall", "wdclr", "not enough memory", "sjson.decoder","sjson.encoder",
"tmr.timer", "table", "string", "number", "function", "director", "collect", "__getInfo", "__setNS", "__checkNS",
"__setFunc", "__restart", "default", "from", "to", "body", "name", "online", "func", "nid",
"connection", "disconnection", "receive", "set", "get", "del", "list", "clear",
'config.json', '__system/flag', 'func.json', '__data/', '__system/swap/'

@ -0,0 +1,488 @@
-- WIOT Lua Firmware on NodeMCU
__main = coroutine.create(function(__run)
--Packages Used: file, sjson, encoder, timer, node, wifi, net, gpio, uart
--Global Constant
-----------------
local CONFIG_PATH = 'config.json';
local FLAG_PATH = '__system/flag';
local FUNC_PATH = 'func.json';
local DB_PREFIX = '__data/';
local SWAP_PREFIX = '__system/swap/';
local SIGNAL_LED = 0;
--For Debug Purpose
--collectgarbage("collect")
--print('Global Constant', node.heap())
--Global Methods (in RAM)
----------------
-- Package Methods
local pack = {
encode = function(obj)
local status, json = pcall(sjson.encode, {obj});
if status then
return string.sub(json, 2, -2);
else
return '';
end
end,
decode = function(str)
if type(str) ~= 'string' then
return nil
end;
local status, obj = pcall(sjson.decode, '['..str..']');
if status then
return obj[1];
else
return nil;
end
end
}
-- File Object Operation
local fs = {
read = function(f)--f:filename
return pack.decode(file.getcontents(f));
end,
write = function(f, obj)
local res = pack.encode(obj);
if res == '' then
return false;
else
while(file.getcontents(f) ~= res) do
file.putcontents(f, res);
end
return true;
end
end
}
--split string
local stringSplit = function(str, reps)
local resultStrList = {}
string.gsub(str,'[^'..reps..']+', function (w)
table.insert(resultStrList,w);
end);
return resultStrList;
end
--For Debug Purpose
--collectgarbage("collect")
--print('Global Methods (in RAM)', node.heap())
-- Signal LED Begin
-------------------
--Mode Init
gpio.mode(SIGNAL_LED, gpio.OUTPUT);
local signal_timer = tmr.create();
--Control Methods
local setSignalInterval = function(interval)
signal_timer:alarm(interval, tmr.ALARM_AUTO, function()
if gpio.read(SIGNAL_LED) == gpio.HIGH then
gpio.write(SIGNAL_LED, gpio.LOW);
else
gpio.write(SIGNAL_LED, gpio.HIGH);
end
end);
end
--SIGNAL: Start
setSignalInterval(200);
--For Debug Purpose
--collectgarbage("collect")
--print('Signal LED Begin', node.heap())
--Load Config (Assume config file is
-- exist and good)
-------------
local config = fs.read(CONFIG_PATH);
--For Debug Purpose
--collectgarbage("collect")
--print('Load Config', node.heap())
--Check Flag (Assume flag is at FLAG_PATH in SPIFF)
--(flag represent the number of startup failures before)
------------
--Load Flag
local flag = file.getcontents(FLAG_PATH);
if flag == nil then
flag = 0;
else
flag = tonumber(flag) + 1;
end
--Update Flag Record in SPIFF
file.putcontents(FLAG_PATH, tostring(flag));
--Failures > 2 Times
if flag > 2 then
--remove func file to run in DEFAULT mode
file.remove(FUNC_PATH);
end
--Release Resource
flag = nil;
--For Debug Purpose
--collectgarbage("collect")
--print('Check Flag', node.heap())
--WiFi Setup
------------
--Set Mode to STA
wifi.setmode(wifi.STATION);
--Set HostName to WIOT-<nid>
wifi.sta.sethostname('WIOT-'..config.nid);
--Configure the WiFi
wifi.sta.config(config.wifi);
--Connect to AP
tmr.create():alarm(1000, tmr.ALARM_AUTO, function(timer)
--print('Setting WiFi');
if wifi.sta.getip() ~= nil then --When WiFi is Ready
--print('WiFi OK');
--Release Timer Resources
timer:unregister();
--Resume Main Process
coroutine.resume(__main);
end
end);
--Suspend Main Process Until WiFi is ready
coroutine.yield();
--For Debug Purpose
--collectgarbage("collect")
--print('WiFi Setup', node.heap())
--SIGNAL: WiFi OK
setSignalInterval(600);
--SWAP Setup (SWAP is a part of space in FLASH
-- specifically storage some low-frequency
-- usage data and methods)
--------------
-- SWAP Method
local swap = {}
function swap:set(key, val, prefix)
return fs.write(prefix..encoder.toBase64(key), val);
end
function swap:get(key, prefix)
return fs.read(prefix..encoder.toBase64(key));
end
function swap:del(key, prefix)
file.remove(prefix..encoder.toBase64(key));
end
function swap:list(prefix)
local fileList = file.list(prefix..'.');
local keys = {};
for k, v in pairs(fileList) do
table.insert(keys, encoder.fromBase64(string.sub(k, #prefix + 1)));
end
return keys;
end
function swap:clear(prefix)
for index, key in pairs(self:list(prefix)) do
self:del(key, prefix);
end
end
--For Debug Purpose
--collectgarbage("collect")
--print('SWAP Setup', node.heap())
--Name Service (NS) Setup
--------------------
local ns = setmetatable({}, {
__index = function(table, key)
local res = swap:get(key, SWAP_PREFIX..'NS/');
if res == nil or res.port == nil or res.ip == nil then
return nil;
else
return res;
end
end,
__newindex = function(table, key, val)
swap:set(key, val, SWAP_PREFIX..'NS/');
end
});
--For Debug Purpose
--collectgarbage("collect")
--print('NS Setup', node.heap())
--MSG Register Init
-------------------
--MSG Register Clear
swap:clear(SWAP_PREFIX..'M/');
--MSG register (in SWAP)
local msgReg = setmetatable({}, {
__index = function(table, key)
local res = swap:get(key, SWAP_PREFIX..'M/');
if res == nil then
return nil;
else
return loadstring(encoder.fromBase64(res));
end
end,
__newindex = function(table, key, val)
if type(val) ~= 'function' then
swap:del(key, SWAP_PREFIX..'M/');
else
swap:set(key, encoder.toBase64(string.dump(val)), SWAP_PREFIX..'M/');
end
end
});
--MSG Reg Method
msgReg_run = function(data)
--decode data
local data = pack.decode(data);
--check data
if type(data) ~= 'table' or type(data.from) ~= 'string' or type(data.name) ~= 'string' or data.to ~= config.nid then
return nil;
end;
--Search mached methods in MSG register
local method = msgReg[data.name];
if type(method) == 'function' then
print(pcall(method, data.from, data.body));
end
end
--For Debug Purpose
--collectgarbage("collect")
--print('MSG Register Init', node.heap())
--UDP Startup
-------------
--Start UDP Service
local udpd = net.createUDPSocket();
udpd:listen(config.msg.port);
udpd:on('receive', function(socket, data, port, ip)
msgReg_run(data);
end);
--For Debug Purpose
--collectgarbage("collect")
--print('UDP Startup', node.heap())
--TCP Startup
-------------
--Start TCP Service
local tcpd = net.createConnection(net.TCP, 0);
local socket = nil;
tcpd:on('connection', function(sck, data)
socket = sck;
--SIGNAL: TCP OK, delete timer object
setSignalInterval = nil;
signal_timer:unregister();
signal_timer = nil;
gpio.write(SIGNAL_LED, gpio.HIGH);
end);
tcpd:on('disconnection', function(sck, data)
socket = nil;
end);
tcpd:on('receive', function(sck, data)
msgReg_run(data);
end);
--connect to director
tmr.create():alarm(3000, tmr.ALARM_AUTO, function()
if socket == nil then
tcpd:connect(config.director.port, config.director.ip);
end
end)
--For Debug Purpose
--collectgarbage("collect")
--print('TCP Startup', node.heap())
--MSG Setup
--------------
-- MSG Methods
local msg = {
send = function(to, name, body, proxy)
local port_ip = ns[to];
local package = pack.encode({
from = config.nid,
to = to,
name = name,
body = body
});
if port_ip == nil or proxy == true then
if socket ~= nil then
socket:send(package);
return ture;
else
return false;
end
end
udpd:send(port_ip.port, port_ip.ip, package);
return true;
end,
onSend = function(name, method)
msgReg[name] = method;
end
}
--For Debug Purpose
--collectgarbage("collect")
--print('MSG Setup', node.heap())
--DB Setup
--------------
--DB Methods
local db = {
set = function(key, val)
return swap:set(key, val, DB_PREFIX);
end,
get = function(key)
return swap:get(key, DB_PREFIX);
end,
del = function(key, prefix)
swap:del(key, DB_PREFIX);
end,
list = function(prefix)
return swap:list(DB_PREFIX);
end,
clear = function()
swap:clear(DB_PREFIX);
end
}
--For Debug Purpose
--collectgarbage("collect")
--print('DB Setup', node.heap())
--Heartbeat Startup
-------------------
local tcpd_heartbeat_timer = tmr.create():alarm(config.director.HeartbeatInterval, tmr.ALARM_AUTO, function()
if socket then
socket:send(config.nid..':'..tostring(tmr.time()));
end
end);
--For Debug Purpose
--collectgarbage("collect")
--print('Heartbeat Startup', node.heap())
--System APIs
-------------
--getInfo API
rawset(msgReg, '__getInfo', function(from, body)
if from == 'director' then
msg.send('director', '__getInfo', {
remainHeap = node.heap(),
remainFS = file.fsinfo(),
msgPort = config.msg.port,
HeartbeatInterval = config.director.HeartbeatInterval,
ns = swap:list(SWAP_PREFIX..'NS/'),
funcID = func.id
}, true);
end
end);
--setNS API
rawset(msgReg, '__setNS', function(from, body)
if from == 'director' and type(body) == 'table' and type(body.nid) == 'string' then
if type(body.port) == 'number' and type(body.ip) == 'string' then
ns[body.nid] = {
port = body.port,
ip = body.ip
}
else
ns[body.nid] = nil;
end
end
end);
--checkNS API
rawset(msgReg, '__checkNS', function(from, body)
if type(body) == 'string' then
if from == 'director' then
msg.send(body, '__checkNS', config.nid);
else
msg.send('director', '__checkNS', body, true);
end
end
end);
--setFunc API
rawset(msgReg, '__setFunc', function(from, body)
if from == 'director' then
if type(body.func) == 'table' and type(body.func.id) == 'string' and type(body.func.online) == 'string' then
fs.write(FUNC_PATH, body.func);
if type(body.ns) == 'table' then
swap:clear(SWAP_PREFIX..'NS/');
for k, v in pairs(body.ns) do
ns[k] = v;
end
end
node.restart();
end
end
end);
--restart API
msgReg['__restart'] = function(from, body)
if from == 'director' then
node.restart();
end
end
--For Debug Purpose
--collectgarbage("collect")
--print('System APIs', node.heap())
--FUNC Startup
--------------
--Load Func to RAM
local func = fs.read(FUNC_PATH);
--Default Mode
if type(func) ~= 'table' or type(func.id) ~= 'string' or type(func.online) ~= 'string' then
func = {
id = 'default',
online = ''
}
end
--warp running
--print(func.id)
local status, errmsg = __run(func.online, db, msg);
--print(status, errmsg)
if status then
tmr.create():alarm(10000, tmr.ALARM_SINGLE, function()
file.remove(FLAG_PATH);
end);
else
node.restart();
end
status, errmsg = nil, nil;
--For Debug Purpose
--collectgarbage("collect")
--print('FUNC Startup', node.heap())
--SIGNAL: FUNC OK
setSignalInterval(1000);
end);
--Boot __main env with a sandbox for user program running
coroutine.resume(__main, function(func, db, msg)
local status, res = pcall(loadstring, 'return function(db, msg) '..func..' end');
if not status then
return status, res;
else
status, res = pcall(res);
if not status then
return status, res;
else
return pcall(res, db, msg);
end
end
return
end);

Binary file not shown.
Loading…
Cancel
Save