diff --git a/lua_examples/telnet/README.md b/lua_examples/telnet/README.md index 5029da29..85075c7b 100644 --- a/lua_examples/telnet/README.md +++ b/lua_examples/telnet/README.md @@ -6,42 +6,19 @@ | 2018-05-24 | [Terry Ellison](https://github.com/TerryE) | [Terry Ellison](https://github.com/TerryE) | [telnet.lua](./telnet.lua) | -The Lua telnet example previously provided in our distro has been moved to this -file `simple_telnet.lua` in this folder. This README discusses the version complex -implementation at the Lua module `telnet.lua`. The main reason for this complex -alternative is that a single Lua command can produce a LOT of output, and the -telnet server has to work within four constraints: +This README discusses the packet marshalling versions of telnet. The first (fifosock) +version was written for SDK 2 implementations, with all of the marshalling imlemented +in Lua; the second (pipe) version uses the latest features added to the SDK 3 version +that have been added to prepare for the `lua53` implementation. These exploit the +stdin / stdout pipe functionality and task integration that is now build into the +NodeNMCU Lua core. -- The SDK rules are that you can only issue one send per task invocation, so any -overflow must be buffered, and the buffer emptied using an on:sent callback (CB). +There are two nice advantages of this core implementation: -- Since the interpeter invokes a node.output CB per field, you have a double whammy -that these fields are typically small, so using a simple array FIFO would rapidly -exhaust RAM. +- Errors are now written to stdout in a spearate task execution. +- The pipes pretty much eliminate uart and telnet overrun. -- For network efficiency, the module aggregates any FIFO buffered into sensible -sized packet, say 1024 bytes, but it must also need to handle the case when larger -string span multiple packets. However, you must flush the buffer if necessary. - -- The overall buffering strategy needs to be reasonably memory efficient and avoid -hitting the GC too hard, so where practical avoid aggregating small strings to more -than 256 chars (as NodeMCU handles \<256 using stack buffers), and avoid serial -aggregation such as buf = buf .. str as this hammers the GC. - -So this server adopts a simple buffering scheme using a two level FIFO. The -`node.output` CB adds records to the 1st level FIFO until the #recs is \> 32 or the -total size would exceed 256 bytes. Once over this threashold, the contents of the -FIFO are concatenated into a 2nd level FIFO entry of upto 256 bytes, and the 1st -level FIFO cleared down to any residue. - -The sender dumps the 2nd level FIFO aggregating records up to 1024 bytes and once this -is empty dumps an aggrate of the 1st level. - -Lastly remember that owing to architectural limitations of the firmware, this server -can only service stdin and stdout. Lua errors are still sent to stderr which is -the UART0 device. Hence errors will fail silently. If you want to capture -errors then you will need to wrap any commands in a `pcall()` and print any -error return. +Both have the same interface if required into the variable `telnet` ## telnet:open() @@ -64,7 +41,7 @@ Nothing returned (this is evaluted as `nil` in a scalar context). ## telnet:close() -Close a telnet server and release all resources. +Close a telnet server and release all resources. Also set the variable `telnet` to nil to fully reference and GC the resources. #### Syntax diff --git a/lua_examples/telnet/simple_telnet.lua b/lua_examples/telnet/simple_telnet.lua deleted file mode 100644 index 3f9525bb..00000000 --- a/lua_examples/telnet/simple_telnet.lua +++ /dev/null @@ -1,35 +0,0 @@ --- a simple telnet server - -telnet_srv = net.createServer(net.TCP, 180) -telnet_srv:listen(2323, function(socket) - local fifo = {} - local fifo_drained = true - - local function sender(c) - if #fifo > 0 then - c:send(table.remove(fifo, 1)) - else - fifo_drained = true - end - end - - local function s_output(str) - table.insert(fifo, str) - if socket ~= nil and fifo_drained then - fifo_drained = false - sender(socket) - end - end - - node.output(s_output, 0) -- re-direct output to function s_ouput. - - socket:on("receive", function(c, l) - node.input(l) -- works like pcall(loadstring(l)) but support multiple separate line - end) - socket:on("disconnection", function(c) - node.output(nil) -- un-regist the redirect output function, output goes to serial - end) - socket:on("sent", sender) - - print("Welcome to NodeMCU world.") -end) diff --git a/lua_examples/telnet/telnet.lua b/lua_examples/telnet/telnet_fifosock.lua similarity index 100% rename from lua_examples/telnet/telnet.lua rename to lua_examples/telnet/telnet_fifosock.lua diff --git a/lua_examples/telnet/telnet_pipe.lua b/lua_examples/telnet/telnet_pipe.lua new file mode 100644 index 00000000..935f5837 --- /dev/null +++ b/lua_examples/telnet/telnet_pipe.lua @@ -0,0 +1,68 @@ +--[[ A telnet server T. Ellison, June 2019 + +This version of the telnet server demonstrates the use of the new stdin and stout +pipes, which is a C implementation of the Lua fifosock concept moved into the +Lua core. These two pipes are referenced in the Lua registry. + +]] + +local M = {} +local modname = ... +local function telnet_session(socket) + local node = node + local stdout, sending + + local function output_CB(opipe) -- upval: socket + stdout = opipe + local rec = opipe:read(1400) + if rec and #rec > 0 then socket:send(rec) end + return false -- don't repost as the on:sent will do this + end + + local function onsent_CB(skt) -- upval: stdout + local rec = stdout:read(1400) + if rec and #rec > 0 then skt:send(rec) end + end + + local function disconnect_CB(skt) -- upval: socket, stdout + node.output() + socket, stdout = nil, nil -- set upvals to nl to allow GC + end + + node.output(output_CB, 0) + socket:on("receive", function(_,rec) node.input(rec) end) + socket:on("sent", onsent_CB) + socket:on("disconnection", disconnect_CB) + print(("Welcome to NodeMCU world (%d mem free, %s)"):format( + node.heap(), wifi.sta.getip())) +end + +function M.open(this, ssid, pwd, port) + local tmr, wifi, uwrite = tmr, wifi, uart.write + if ssid then + wifi.setmode(wifi.STATION, false) + wifi.sta.config { ssid = ssid, pwd = pwd, save = false } + end + local t = tmr.create() + t:alarm(500, tmr.ALARM_AUTO, function() + if (wifi.sta.status() == wifi.STA_GOTIP) then + t:unregister() + t=nil + print(("Telnet server started (%d mem free, %s)"):format( + node.heap(), wifi.sta.getip())) + M.svr = net.createServer(net.TCP, 180) + M.svr:listen(port or 23, telnet_session) + else + uwrite(0,".") + end + end) +end + +function M.close(this) + if this.svr then this.svr:close() end + package.loaded[modname] = nil +end + +return M + +