dev
IoTcat 3 years ago
parent b2bd5ed46c
commit afc71749ab
  1. 38
      src/director/archieve/index.js
  2. 0
      src/director/archieve/modules/heartbeat.js
  3. 0
      src/director/archieve/modules/lib/cache.js
  4. 0
      src/director/archieve/modules/lib/coap.js
  5. 0
      src/director/archieve/modules/lib/crypt.js
  6. 0
      src/director/archieve/modules/lib/http.js
  7. 0
      src/director/archieve/modules/lib/log.js
  8. 0
      src/director/archieve/modules/lib/reg.js
  9. 0
      src/director/archieve/modules/serve.js
  10. 35
      src/director/index.js
  11. 173
      src/director/modules/nodetable.js
  12. 12
      src/director/package.json
  13. 81
      src/director/yarn.lock
  14. BIN
      src/drivers/nodemcu/lua/lfs.img
  15. BIN
      src/drivers/nodemcu/lua/lfs.zip
  16. 2
      src/drivers/nodemcu/lua/lfs/dummy_strings.lua
  17. 69
      src/drivers/nodemcu/lua/lfs/wiot.lua
  18. 87
      src/drivers/nodemcu/lua/lfs_original/_init.lua
  19. 37
      src/drivers/nodemcu/lua/lfs_original/dummy_strings.lua
  20. 488
      src/drivers/nodemcu/lua/lfs_original/wiot.lua

@ -0,0 +1,38 @@
const heartbeat = require(__dirname + '/modules/heartbeat.js')();
const app = require('express')();
app.listen(8081);
app.get('/', async function (req, res) {
if(!req.query.hasOwnProperty('id') || !req.query.hasOwnProperty('fid') || !req.query.hasOwnProperty('body')){
res.send('Illegal params');
return;
}
console.log(req.query.body)
let msg = await heartbeat.push(req.query.id, req.query.fid, JSON.parse(new Buffer(req.query.body, 'base64').toString())[0]);
res.send(msg);
})
;(async () => {
//console.log('aaaaaaaaaaaaaaa: ', await heartbeat.push('test1', 'info', ''))
//heartbeat.push('test1', 'test', 'test1', (r)=>{
//console.log(r)
//})
//heartbeat.push('test1', 'exec', 'pwm.setup(2, 100, 512);pwm.start(2);local n = 0; local timer = tmr.create(); timer:register(10, tmr.ALARM_AUTO, function() pwm.setduty(2, n%1024); n = n + 1; end);timer:start();');
//console.log(await heartbeat.push('test1', 'exec', 'return adc.read(0)'))
})()

@ -1,38 +1,5 @@
const heartbeat = require(__dirname + '/modules/heartbeat.js')();
const app = require('express')();
const nodetable = require(__dirname + '/modules/nodetable.js')();
app.listen(8081);
app.get('/', async function (req, res) {
if(!req.query.hasOwnProperty('id') || !req.query.hasOwnProperty('fid') || !req.query.hasOwnProperty('body')){
res.send('Illegal params');
return;
}
console.log(req.query.body)
let msg = await heartbeat.push(req.query.id, req.query.fid, JSON.parse(new Buffer(req.query.body, 'base64').toString())[0]);
res.send(msg);
})
;(async () => {
//console.log('aaaaaaaaaaaaaaa: ', await heartbeat.push('test1', 'info', ''))
//heartbeat.push('test1', 'test', 'test1', (r)=>{
//console.log(r)
//})
//heartbeat.push('test1', 'exec', 'pwm.setup(2, 100, 512);pwm.start(2);local n = 0; local timer = tmr.create(); timer:register(10, tmr.ALARM_AUTO, function() pwm.setduty(2, n%1024); n = n + 1; end);timer:start();');
//console.log(await heartbeat.push('test1', 'exec', 'return adc.read(0)'))
})()

@ -0,0 +1,173 @@
module.exports = (host = '0.0.0.0', port = 4444) => {
let cbArr = {
income: [],
outcome: [],
forward: [],
error: []
};
let o = {
income: cb => { //cb(nid, info{funcID, error}, data)
cbArr.income.push(cb);
},
forward: cb => { //cb(nid, info{funcID, error}, data)
cbArr.forward.push(cb);
},
outgo: (nid, data) => {
if(!nidtable.hasOwnProperty(nid) || !nidtable[nid].socket){
flow.error('[OUTGOING]', '<nid lookup failure>', data.to+'<--'+data.from, data.name, data.body);
return false;
}
nidtable[nid].socket.write(JSON.stringify(data));
flow.log('[OUTGOING]', data.from + '-->' + data.to, data.name, data.body);
return true;
}
}
let nidtable = {}
function isJson(str) {
try {
if (typeof JSON.parse(str) == "object") {
return true;
}
} catch(e) {
}
return false;
}
const LOG_PATH = __dirname + '/../data/log/iptable/';
const net = require('net');
const log4js = require('log4js');
log4js.configure({
appenders: {
flow: {type: 'file', filename: LOG_PATH + 'flow.log'},
access: {type: 'file', filename: LOG_PATH + 'access.log'},
console: { type: 'console' }
},
categories: {
flow: {appenders: ['flow', 'console'], level: 'trace' },
access: { appenders: ['access', 'console'], level: 'trace' },
default: { appenders: ['access', 'console'], level: 'trace' }
}
});
const flow = log4js.getLogger('flow');
const access = log4js.getLogger('access');
const server = net.createServer((socket) => {
access.trace('Unspecified', socket.remoteAddress+':'+socket.remotePort, 'New TCP request.');
let nid = null;
socket.on('data', (data) => {
data = data.toString();
if(!isJson(data)){
access.error(nid, socket.remoteAddress+':'+socket.remotePort, 'Data is not JSON! Data::'+data);
return;
}
data = JSON.parse(data);
if(data.hasOwnProperty('nid') && data.hasOwnProperty('funcID')){
nid = data.nid;
if(!nidtable.hasOwnProperty(nid)){
nidtable[nid] = {};
}
nidtable[nid].funcID = data.funcID;
nidtable[nid].ip = socket.remoteAddress;
nidtable[nid].ip = socket.remotePort;
nidtable[nid].socket = socket;
if(data.hasOwnProperty('error') && data.error){
nidtable[nid].error = data.error;
}
access.info(nid, socket.remoteAddress+':'+socket.remotePort, 'New TCP connection.');
return;
}
if(!nid){
access.error(nid, socket.remoteAddress+':'+socket.remotePort, 'No nid is specified. Data::', data);
return;
}
if(data.hasOwnProperty('uptime') && data.hasOwnProperty('heap') && data.hasOwnProperty('spiff')){
nidtable[nid].LastUpTime = new Date().valueOf() - data.uptime*1000;
nidtable[nid].heap = data.heap;
nidtable[nid].spiff = data.spiff;
access.info(nid, socket.remoteAddress+':'+socket.remotePort, '[HEARTBEAT]', data);
return;
}
if(!data.hasOwnProperty('from') || !data.hasOwnProperty('to') || !data.hasOwnProperty('name') || !data.hasOwnProperty('body')){
access.error(nid, socket.remoteAddress+':'+socket.remotePort, 'Illegal package format. Data::', data);
return;
}
if(data.to != 'director'){
if(nidtable.hasOwnProperty(data.to)){
nidtable[data.to].socket.write(JSON.stringify(data));
flow.log('[FORWARD]', data.to+'<--'+data.from, data.name, data.body);
}else{
flow.error('[FORWARD]', '<nid lookup failure>', data.to+'<--'+data.from, data.name, data.body);
}
cbArr.forward.forEach(cb => {
cb(nid, nidtable[nid], data);
});
return;
}
if(data.to == 'director'){
cbArr.income.forEach(cb => {
cb(nid, nidtable[nid], data);
});
flow.log('[INCOMING]', data.to+'<--'+data.from, data.name, data.body);
return;
}
});
socket.on('close', function(err){
if(nid){
nidtable[nid].socket = null;
nidtable[nid].ip = null;
nidtable[nid].port = null;
}
})
}).on('error', (err) => {
throw err;
});
// Grab an arbitrary unused port.
server.listen({
host: host,
port: port,
exclusive: true
}, () => {
access.info('TCP Server Begin at ', host + ':' + port);
});
return o;
}

@ -0,0 +1,12 @@
{
"name": "wiot-director",
"version": "0.0.1",
"description": "Director for wIoT systems.",
"main": "index.js",
"repository": "https://wiot.js.org",
"author": "iotcat",
"license": "MIT",
"dependencies": {
"log4js": "^6.3.0"
}
}

@ -0,0 +1,81 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
date-format@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
date-format@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95"
integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==
debug@^4.1.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
flatted@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
optionalDependencies:
graceful-fs "^4.1.6"
log4js@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb"
integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==
dependencies:
date-format "^3.0.0"
debug "^4.1.1"
flatted "^2.0.1"
rfdc "^1.1.4"
streamroller "^2.2.4"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
rfdc@^1.1.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.2.0.tgz#9e9894258f48f284b43c3143c68070a4f373b949"
integrity sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==
streamroller@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53"
integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==
dependencies:
date-format "^2.1.0"
debug "^4.1.1"
fs-extra "^8.1.0"
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==

Binary file not shown.

Binary file not shown.

@ -9,4 +9,4 @@ local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"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/'
'config.json', '__system/flag', 'func.json', '__data/', '__system/swap/', '__system/error', 'Unknown Error occurs before 10s after func up'

@ -6,6 +6,7 @@ __main = coroutine.create(function(__run)
-----------------
local CONFIG_PATH = 'config.json';
local FLAG_PATH = '__system/flag';
local ERROR_PATH = '__system/error';
local FUNC_PATH = 'func.json';
local DB_PREFIX = '__data/';
local SWAP_PREFIX = '__system/swap/';
@ -322,12 +323,32 @@ __main = coroutine.create(function(__run)
-------------
--Start TCP Service
local tcpd = net.createConnection(net.TCP, 0);
--Set the socket variable to nil as a signal of disconnect
local socket = nil;
--Set the socket status variable to false as a signal of disconnect
local socket = {
queue = {},
lock = false
};
--Send message in queue
function socket:send(str)
table.insert(socket.queue, str);
if #socket.queue > 0 and socket.sck ~= nil and socket.lock == false then
socket.sck:send(socket.queue[1]);
socket.lock = true;
end
end
--when tcp is connected
tcpd:on('connection', function(sck, data)
tcpd:on('connection', function(sck, data) print('c', #socket.queue)
--set socket variable to alive socket object
socket = sck;
socket.sck = sck;
socket.lock = false;
--send node info to director
socket:send(pack.encode({
nid = config.nid,
funcID = func.id,
ip = wifi.sta.getip(),
error = file.getcontents(ERROR_PATH)
}));
file.remove(ERROR_PATH);
--SIGNAL: TCP OK, release timer object
signal_timer:unregister();
if func.id == 'default' then
@ -339,9 +360,10 @@ __main = coroutine.create(function(__run)
end
end);
--when tcp is disconnect or a connect try timeout
tcpd:on('disconnection', function(sck, data)
tcpd:on('disconnection', function(sck, data) print('d', #socket.queue)
--set socket variable to nil as a signal of disconnect
socket = nil;
socket.sck = nil;
socket.lock = true;
--reconnect after 1s
tmr.create():alarm(1000, tmr.ALARM_SINGLE, function()
tcpd:connect(config.director.port, config.director.ip);
@ -349,6 +371,16 @@ __main = coroutine.create(function(__run)
--SIGNAL: TCP BAD
setSignalInterval(1000);
end);
--when tcp sent
tcpd:on('sent', function(sck) print('s', #socket.queue)
table.remove(socket.queue, 1);
if #socket.queue > 0 and socket.sck ~= nil then
sck:send(socket.queue[1]);
socket.lock = true;
else
socket.lock = false;
end
end);
--when tcp incomming message
tcpd:on('receive', function(sck, data)
--send message to msg register
@ -385,7 +417,7 @@ __main = coroutine.create(function(__run)
--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 socket.sck ~= nil then
--if tcp is alive
socket:send(package);
return true;
@ -443,8 +475,13 @@ __main = coroutine.create(function(__run)
-- 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()));
if socket.sck then
--socket send
socket:send(pack.encode({
uptime = tmr.time(),
heap = node.heap(), --remain RAM
spiff = file.fsinfo() --remain Flash storage
}));
end
end);
@ -461,12 +498,12 @@ __main = coroutine.create(function(__run)
if from == 'director' then
--Send info package to director
msg.send('director', '__getInfo', {
remainHeap = node.heap(), --remain RAM
remainFS = file.fsinfo(), --remain Flash storage
nid = config.nid,
funcID = func.id,
ip = wifi.sta.getip(),
msgPort = config.msg.port,
HeartbeatInterval = config.director.HeartbeatInterval,
ns = swap:list(SWAP_PREFIX..'NS/'),
funcID = func.id
ns = swap:list(SWAP_PREFIX..'NS/')
}, true);
end
end);
@ -534,12 +571,18 @@ __main = coroutine.create(function(__run)
local status, errmsg = __run(func.online, db, msg);
--print(status, errmsg)
if status then
--prerecord errormsg
file.putcontents(ERROR_PATH, 'Unknown Error occurs before 10s after func up');
--flag watchdog, handle unknown error
tmr.create():alarm(10000, tmr.ALARM_SINGLE, function()
--the startup of user program is successful
file.remove(FLAG_PATH);
--remove the prerecord errormsg
file.remove(ERROR_PATH);
end);
else
--record error
file.putcontents(ERROR_PATH, errmsg);
--user program startup fail, restart system
node.restart();
end

@ -1,87 +0,0 @@
--
-- 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

@ -1,37 +0,0 @@
--
-- 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/'

@ -1,488 +0,0 @@
-- 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);
Loading…
Cancel
Save