Add telnet example
This commit is contained in:
parent
e7c29fe38e
commit
50b69d8487
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue