--[[ A telnet server T. Ellison, May 2018 This version is more complex than the simple Lua example previously provided in our distro. The main reason is that a single Lua command can produce a LOT of output, and the server has to work within four constraints: - 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 cb - 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. - For network efficiency, you want to aggregate any FIFO buffered into sensible sized packet, say 1024 bytes, but you 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 cb 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. ]] --luacheck: no unused args local node, tmr, wifi, uwrite = node, tmr, wifi, uart.write local function telnet_listener(socket) local queueLine = (require "fifosock").wrap(socket) local function receiveLine(s, line) node.input(line) end local function disconnect(s) socket:on("disconnection", nil) socket:on("reconnection", nil) socket:on("connection", nil) socket:on("receive", nil) socket:on("sent", nil) node.output(nil) end socket:on("receive", receiveLine) socket:on("disconnection", disconnect) node.output(queueLine, 0) print(("Welcome to NodeMCU world (%d mem free, %s)"):format(node.heap(), wifi.sta.getip())) end local listenerSocket return { open = function(this, ssid, pwd, port) 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())) net.createServer(net.TCP, 180):listen(port or 23, telnet_listener) else uwrite(0,".") end end) end, close = function(this) if listenerSocket then listenerSocket:close() package.loaded.telnet = nil listenerSocket = nil collectgarbage() end end, }