Browse Source

Make the telnet example an Lua module (#3133)

Also update ftp server
Marcel Stör 3 years ago
parent
commit
8d091c476e

+ 50 - 0
docs/lua-modules/telnet.md

@@ -0,0 +1,50 @@
+# Telnet Module
+
+| Since  | Origin / Contributor  | Maintainer  | Source  |
+| :----- | :-------------------- | :---------- | :------ |
+| 2018-05-24 | [Terry Ellison](https://github.com/TerryE) |  [Terry Ellison](https://github.com/TerryE) | [telnet.lua](../../lua_modules/telnet/telnet.lua) |
+
+The current version of this module exploits the stdin / stdout pipe functionality and 
+task integration that is now build into the NodeNMCU Lua core. 
+
+There are two nice advantages of this core implementation:
+
+-  Errors are now written to stdout in a separate task execution.
+-  The pipes pretty much eliminate UART and telnet overrun.
+
+Both have the same interface if required into the variable `telnet`
+
+## telnet:open()
+
+Open a telnet server based on the provided parameters.
+
+#### Syntax
+
+`telnet:open(ssid, pwd, port)`
+
+#### Parameters
+
+`ssid` and `password`.  Strings.  SSID and Password for the Wifi network.  If these are
+`nil` then the wifi is assumed to be configured or auto-configured.
+
+`port`.  Integer TCP listening port for the Telnet service.  The default is 2323
+
+#### Returns
+
+Nothing returned (this is evaluated as `nil` in a scalar context).
+
+## telnet:close()
+
+Close a telnet server and release all resources.  Also set the variable `telnet` to nil to fully reference and GC the resources.
+
+#### Syntax
+
+`telnet:close()`
+
+#### Parameters
+
+None
+
+#### Returns
+
+Nothing returned (this is evaluated as `nil` in a scalar context).

+ 0 - 56
lua_examples/telnet/README.md

@@ -1,56 +0,0 @@
-# Telnet Module
-
-| Since  | Origin / Contributor  | Maintainer  | Source  |
-| :----- | :-------------------- | :---------- | :------ |
-| 2014-12-22 | [Zeroday](https://github.com/funshine) |  [Terry Ellison](https://github.com/TerryE) | [simple_telnet.lua](./simple_telnet.lua) |
-| 2018-05-24 | [Terry Ellison](https://github.com/TerryE) |  [Terry Ellison](https://github.com/TerryE) | [telnet.lua](./telnet.lua) |
-
-
-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. 
-
-There are two nice advantages of this core implementation:
-
--  Errors are now written to stdout in a spearate task execution.
--  The pipes pretty much eliminate uart and telnet overrun.
-
-Both have the same interface if required into the variable `telnet`
-
-## telnet:open()
-
-Open a telnet server based on the provided parameters.
-
-#### Syntax
-
-`telnet:open(ssid, pwd, port)`
-
-#### Parameters
-
-`ssid` and `password`.  Strings.  SSID and Password for the Wifi network.  If these are
-`nil` then the wifi is assumed to be configured or autoconfigured.
-
-`port`.  Integer TCP listenting port for the Telnet service.  The default is 2323
-
-#### Returns
-
-Nothing returned (this is evaluted as `nil` in a scalar context).
-
-## telnet:close()
-
-Close a telnet server and release all resources.  Also set the variable `telnet` to nil to fully reference and GC the resources.
-
-#### Syntax
-
-`telnet:close()`
-
-#### Parameters
-
-None
-
-#### Returns
-
-Nothing returned (this is evaluted as `nil` in a scalar context).

+ 0 - 84
lua_examples/telnet/telnet_fifosock.lua

@@ -1,84 +0,0 @@
---[[  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,
-}

+ 190 - 180
lua_modules/ftp/ftpserver.lua

@@ -1,8 +1,10 @@
+--[[SPLIT MODULE ftp]]
+
 --[[ A simple ftp server
 
  This is my implementation of a FTP server using Github user Neronix's
- example as inspriration, but as a cleaner Lua implementation that has been
- optimised for use in LFS. The coding style adopted here is more similar to
+ example as inspriration, but as a cleaner Lua implementation that is
+ suitable for use in LFS. The coding style adopted here is more similar to
  best practice for normal (PC) module implementations, as using LFS enables
  me to bias towards clarity of coding over brevity. It includes extra logic
  to handle some of the edge case issues more robustly. It also uses a
@@ -13,113 +15,116 @@
  with any multiple calls requected, so FTP is a singleton static object.
  However there is nothing to stop multiple clients connecting to the FTP
  listener at the same time, and indeed some FTP clients do use multiple
- connections, so this server can accept and create multiple CON objects.
- Each CON object can also have a single DATA connection.
+ connections, so this server can accept and create multiple cxt objects.
+ Each cxt object can also have a single DATA connection.
 
  Note that FTP also exposes a number of really private properties (which
  could be stores in local / upvals) as FTP properties for debug purposes.
+
+ Note that this version has now been updated to allow the main methods to
+ be optionally loaded lazily, and SPILT comments allow the source to be
+ preprocessed for loading as either components in the "fast" Cmodule or as
+ LC files in SPIFFS.
 ]]
-local file, net, wifi, node, table, tmr, pairs, print, pcall, tostring =
-      file, net, wifi, node, table, tmr, pairs, print, pcall, tostring
-local post = node.task.post
-local FTP, cnt = {client = {}}, 0
-
--- Local functions
-
-local processCommand    -- function(cxt, sock, data)
-local processBareCmds   -- function(cxt, cmd)
-local processSimpleCmds -- function(cxt, cmd, arg)
-local processDataCmds   -- function(cxt, cmd, arg)
-local dataServer        -- function(cxt, n)
-local ftpDataOpen       -- function(dataSocket)
-
--- Note these routines all used hoisted locals such as table and debug as
--- upvals for performance (ROTable lookup is slow on NodeMCU Lua), but
--- data upvals (e.g. FTP) are explicitly list is -- "upval:" comments.
-
--- Note that the space between debug and the arglist is there for a reason
--- so that a simple global edit "   debug(" -> "-- debug(" or v.v. to
--- toggle debug compiled into the module.
-
-local function debug (fmt, ...) -- upval: cnt (, print, node, tmr)
-  if not FTP.debug then return end
-  if (...) then fmt = fmt:format(...) end
-  print(node.heap(),fmt)
-  cnt = cnt + 1
-  if cnt % 10 then tmr.wdclr() end
+--luacheck: read globals fast file net node tmr uart wifi FAST_ftp SPIFFS_ftp
+
+local FTP, FTPindex = {client = {}}, nil
+
+if FAST_ftp then
+  function FTPindex(_, name) return fast.load('ftp-'..name) end
+elseif SPIFFS_ftp then
+  function FTPindex(_, name) return loadfile('ftp-'..name..'.lc') end
 end
 
+if FTPindex then return setmetatable(FTP,{__index=FTPindex}) end
+
+function FTP.open(...)  --[[SPLIT HERE ftp-open]]
 --------------------------- Set up the FTP object ----------------------------
 --       FTP has three static methods: open, createServer and close
 ------------------------------------------------------------------------------
 
 -- optional wrapper around createServer() which also starts the wifi session
-function FTP.open(user, pass, ssid, pwd, dbgFlag) -- upval: FTP (, wifi, tmr, print)
+-- Lua: FTP:open(user, pass, ssid, pwd[, dbgFlag])
+
+local this, user, pass, ssid, pwd, dbgFlag = ...
+
   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()
+  tmr.create():alarm(500, tmr.ALARM_AUTO, function(t) -- this: FTP, user, pass, dbgFlag
     if (wifi.sta.status() == wifi.STA_GOTIP) then
       t:unregister()
-      t=nil
       print("Welcome to NodeMCU world", node.heap(), wifi.sta.getip())
-      return FTP.createServer(user, pass, dbgFlag)
+      return this:createServer(user, pass, dbgFlag)
     else
       uart.write(0,".")
     end
   end)
-end
-
-
-function FTP.createServer(user, pass, dbgFlag)  -- upval: FTP (, debug, tostring, pcall, type, processCommand)
-  FTP.user, FTP.pass, FTP.debug = user, pass, dbgFlag
-  FTP.server = net.createServer(net.TCP, 180)
-  _G.FTP = FTP
-  debug("Server created: (userdata) %s", tostring(FTP.server))
 
-  FTP.server:listen(21, function(sock) -- upval: FTP (, debug, pcall, type, processCommand)
+end --[[SPLIT IGNORE]]
+function FTP.createServer(...)  --[[SPLIT HERE ftp-createServer]]
+-- Lua: FTP:createServer(user, pass[, dbgFlag])
+  local this, user, pass, dbgFlag = ...
+  local cnt = 0
+  this.user, this.pass, dbgFlag = user, pass, (dbgFlag and true or false)
+
+  this.debug = (not dbgFlag) and type -- executing type(...) is in effect a NOOP
+             or function(fmt, ...) -- upval: cnt
+                if (...) then fmt = fmt:format(...) end
+                print(node.heap(),fmt)
+                cnt = cnt + 1
+                if cnt % 10 then tmr.wdclr() end
+              end
+
+  this.server = net.createServer(net.TCP, 180)
+  _G.FTP = this
+  this.debug("Server created: (userdata) %s", tostring(this.server))
+
+  this.server:listen(21, function(sock) -- upval: this
       -- since a server can have multiple connections, each connection
-      -- has a CNX table to store connection-wide globals.
-      local client = FTP.client
-      local CNX; CNX = {
+      -- has its own CXN object (table) to store connection-wide globals.
+      local CXN; CXN = {
         validUser = false,
         cmdSocket = sock,
-        send      = function(rec, cb) -- upval: CNX (,debug)
-         -- debug("Sending: %s", rec)
-            return CNX.cmdSocket:send(rec.."\r\n", cb)
-          end, --- send()
-        close    = function(socket) -- upval: client, CNX (,debug, pcall, type)
-         -- debug("Closing CNX.socket=%s, sock=%s", tostring(CNX.socket), tostring(sock))
+        debug     = this.debug,
+        FTP       = this,
+        send      = function(rec, cb) -- upval: CXN
+            CXN.debug("Sending: %s", rec)
+            return CXN.cmdSocket:send(rec.."\r\n", cb)
+          end, --- CXN. send()
+        close    = function(socket)   -- upval: CXN
+             CXN.debug("Closing CXN.cmdSocket=%s", tostring(CXN.cmdSocket))
             for _,s in ipairs{'cmdSocket', 'dataServer', 'dataSocket'} do
-              local sck; sck,CNX[s] = CNX[s], nil
-           -- debug("closing CNX.%s=%s", s, tostring(sck))
-              if type(sck)=='userdata' then pcall(sck.close, sck) end
+               CXN.debug("closing CXN.%s=%s", s, tostring(CXN[s]))
+              if type(CXN[s])=='userdata' then
+                pcall(socket.close, CXN[s])
+                CXN[s]= nil
+              end
             end
-            client[socket] = nil
-          end -- CNX.close()
+            CXN.FTP.client[socket] = nil
+          end -- CXN.close()
         }
 
-      local function validateUser(socket, data) -- upval: CNX, FTP (, debug, processCommand)
+      local function validateUser(socket, data) -- upval: CXN
         -- validate the logon and if then switch to processing commands
-
-     -- debug("Authorising: %s", data)
+         CXN.debug("Authorising: %s", data)
         local cmd, arg = data:match('([A-Za-z]+) *([^\r\n]*)')
         local msg =  "530 Not logged in, authorization required"
         cmd = cmd:upper()
 
         if   cmd == 'USER' then
-          CNX.validUser = (arg == FTP.user)
-          msg = CNX.validUser and
+          CXN.validUser = (arg == CXN.FTP.user)
+          msg = CXN.validUser and
                  "331 OK. Password required" or
                  "530 user not found"
 
-        elseif CNX.validUser and cmd == 'PASS' then
-          if arg == FTP.pass then
-            CNX.cwd = '/'
-            socket:on("receive", function(socketObj, dataObj)
-                processCommand(CNX,socketObj, dataObj)
+        elseif CXN.validUser and cmd == 'PASS' then
+          if arg == CXN.FTP.pass then
+            CXN.cwd = '/'
+            socket:on("receive", function(soc, rec) -- upval: CXN
+                assert(soc==CXN.cmdSocket)
+                CXN.FTP.processCommand(CXN, rec)
               end) -- logged on so switch to command mode
             msg = "230 Login successful. Username & password correct; proceed."
           else
@@ -128,46 +133,45 @@ function FTP.createServer(user, pass, dbgFlag)  -- upval: FTP (, debug, tostring
 
         elseif cmd == 'AUTH' then
           msg = "500 AUTH not understood"
-
         end
 
-        return CNX.send(msg)
+        return CXN.send(msg)
       end
 
     local port,ip = sock:getpeer() -- luacheck: no unused
-    --debug("Connection accepted: (userdata) %s client %s:%u", tostring(sock), ip, port)
+    --cxt.debug("Connection accepted: (userdata) %s client %s:%u", tostring(sock), ip, port)
     sock:on("receive",       validateUser)
-    sock:on("disconnection", CNX.close)
-    FTP.client[sock]=CNX
-
-    CNX.send("220 FTP server ready");
-  end) -- FTP.server:listen()
-end -- FTP.createServer()
+    sock:on("disconnection", CXN.close)
+    this.client[sock]=CXN
 
+    CXN.send("220 FTP server ready");
+  end) -- this.server:listen()
+end --[[SPLIT IGNORE]]
+function FTP.close(...)  --[[SPLIT HERE ftp-close]]
+-- Lua: FTP:close()
 
-function FTP.close() -- upval: FTP (, debug, post, tostring)
-  local svr = FTP.server
+local this = ...
 
-  local function rollupClients(client, server) -- upval: FTP (,debug, post, tostring, rollupClients)
-    -- this is done recursively so that we only close one client per task
-    local skt,cxt = next(client)
+  -- this.client is a table of soc = cnx.  The first (and usually only connection) is cleared
+  -- immediately and next() used to do a post chain so we only close one client per task
+  local function rollupClients(skt,cxt)  -- upval: this, rollupClients
     if skt then
-   -- debug("Client close: %s", tostring(skt))
+      this.debug("Client close: %s", tostring(skt))
       cxt.close(skt)
-      post(function() return rollupClients(client, server) end) -- upval: rollupClients, client, server
-    else
-   -- debug("Server close: %s", tostring(server))
-      server:close()
-      server:__gc()
-      FTP,_G.FTP = nil, nil -- the upval FTP can only be zeroed once FTP.client is cleared.
+      this.client[skt] = nil
+      node.task.post(function() return rollupClients(next(this.client, skt)) end) -- upval: rollupClients, this, skt
+    else -- we have emptied the open socket table, so can now shut the server
+      this.debug("Server close: %s", tostring(this. server))
+      this.server:close()
+      this.server:__gc()
+      _G.FTP = nil
     end
   end
+  rollupClients(next(this.client))
+  package.loaded.ftpserver = nil
 
-  if svr then rollupClients(FTP.client, svr) end
-  package.loaded.ftpserver=nil
-end -- FTP.close()
-
-
+end --[[SPLIT IGNORE]]
+function FTP.processCommand(...)  --[[SPLIT HERE ftp-processCommand]]
 ----------------------------- Process Command --------------------------------
 -- This splits the valid commands into one of three categories:
 --   *  bare commands (which take no arg)
@@ -177,28 +181,30 @@ end -- FTP.close()
 --
 -- Find strings are used do this lookup and minimise long if chains.
 ------------------------------------------------------------------------------
--- upvals: (, debug, processBareCmds, processSimpleCmds, processDataCmds)
-processCommand = function(cxt, socket, data) -- luacheck: no unused
-  debug("Command: %s", data)
+
+local cxt, data = ...
+
+  cxt.debug("Command: %s", data)
   data = data:gsub('[\r\n]+$', '') -- chomp trailing CRLF
   local cmd, arg = data:match('([a-zA-Z]+) *(.*)')
   cmd = cmd:upper()
   local _cmd_ = '_'..cmd..'_'
-
   if ('_CDUP_NOOP_PASV_PWD_QUIT_SYST_'):find(_cmd_) then
-    processBareCmds(cxt, cmd)
+    cxt.FTP.processBareCmds(cxt, cmd)
   elseif ('_CWD_DELE_MODE_PORT_RNFR_RNTO_SIZE_TYPE_'):find(_cmd_) then
-    processSimpleCmds(cxt, cmd, arg)
+    cxt.FTP.processSimpleCmds(cxt, cmd, arg)
   elseif ('_LIST_NLST_RETR_STOR_'):find(_cmd_) then
-    processDataCmds(cxt, cmd, arg)
+    cxt.FTP.processDataCmds(cxt, cmd, arg)
   else
     cxt.send("500 Unknown error")
   end
-end -- processCommand(sock, data)
-
 
+end --[[SPLIT IGNORE]]
+function FTP.processBareCmds(...)  --[[SPLIT HERE ftp-processBareCmds]]
 -------------------------- Process Bare Commands -----------------------------
-processBareCmds = function(cxt, cmd) -- upval: (dataServer)
+
+local cxt, cmd = ...
+
 
   local send = cxt.send
 
@@ -220,7 +226,7 @@ processBareCmds = function(cxt, cmd) -- upval: (dataServer)
     pplo = port % 256
     pphi = (port-pplo)/256
     i1,i2,i3,i4 = ip:match("(%d+).(%d+).(%d+).(%d+)")
-    dataServer(cxt, port)
+    cxt.FTP.dataServer(cxt, port)
     return send(
        ('227 Entering Passive Mode(%d,%d,%d,%d,%d,%d)'):format(
          i1,i2,i3,i4,pphi,pplo))
@@ -229,7 +235,7 @@ processBareCmds = function(cxt, cmd) -- upval: (dataServer)
     return send('257 "/" is the current directory')
 
   elseif cmd == 'QUIT' then
-    send("221 Goodbye", function() cxt.close(cxt.cmdSocket) end)
+    send("221 Goodbye", function() cxt.close(cxt.cmdSocket) end) -- upval: cxt
     return
 
   elseif cmd == 'SYST' then
@@ -239,11 +245,14 @@ processBareCmds = function(cxt, cmd) -- upval: (dataServer)
   else
     error('Oops.  Missed '..cmd)
   end
-end -- processBareCmds(cmd, send)
+
+end --[[SPLIT IGNORE]]
+function FTP.processSimpleCmds(...)  --[[SPLIT HERE ftp-processSimpleCmds]]
 
 ------------------------- Process Simple Commands ----------------------------
-local from  -- needs to persist between simple commands
-processSimpleCmds = function(cxt, cmd, arg)  -- upval: from (, file, tostring, dataServer, debug)
+
+local cxt, cmd, arg = ...
+
 
   local send = cxt.send
 
@@ -252,7 +261,7 @@ processSimpleCmds = function(cxt, cmd, arg)  -- upval: from (, file, tostring, d
                                "504 Only S(tream) is suported")
 
   elseif cmd == 'PORT' then
-    dataServer(cxt,nil) -- clear down any PASV setting
+    cxt.FTP.dataServer(cxt,nil) -- clear down any PASV setting
     return send("502 Active mode not supported. PORT not implemented")
 
   elseif cmd == 'TYPE' then
@@ -269,7 +278,7 @@ processSimpleCmds = function(cxt, cmd, arg)  -- upval: from (, file, tostring, d
 
   -- The remaining commands take a filename as an arg. Strip off leading / and ./
   arg = arg:gsub('^%.?/',''):gsub('^%.?/','')
-  debug("Filename is %s",arg)
+  cxt.debug("Filename is %s",arg)
 
   if cmd == 'CWD' then
     if arg:match('^[%./]*$') then
@@ -285,14 +294,14 @@ processSimpleCmds = function(cxt, cmd, arg)  -- upval: from (, file, tostring, d
     return send("550 Requested action not taken")
 
   elseif cmd == 'RNFR' then
-    from = arg
+    cxt.from = arg
     send("350 RNFR accepted")
     return
 
   elseif cmd == 'RNTO' then
-    local status = from and file.rename(from, arg)
- -- debug("rename('%s','%s')=%s", tostring(from), tostring(arg), tostring(status))
-    from = nil
+    local status = cxt.from and file.rename(cxt.from, arg)
+    cxt.debug("rename('%s','%s')=%s", tostring(cxt.from), tostring(arg), tostring(status))
+    cxt.from = nil
     return send(status and "250 File renamed" or
                             "550 Requested action not taken")
   elseif cmd == "SIZE" then
@@ -303,16 +312,19 @@ processSimpleCmds = function(cxt, cmd, arg)  -- upval: from (, file, tostring, d
   else
     error('Oops.  Missed '..cmd)
   end
-end -- processSimpleCmds(cmd, arg, send)
 
+end --[[SPLIT IGNORE]]
+function FTP.processDataCmds(...)  --[[SPLIT HERE ftp-processDataCmds]]
 
 -------------------------- Process Data Commands -----------------------------
-processDataCmds = function(cxt, cmd, arg)  -- upval: FTP (, pairs, file, tostring, debug, post)
 
-  local send = cxt.send
+local cxt, cmd, arg = ...
+
+
+  local send, FTP = cxt.send, cxt.FTP -- luacheck: ignore FTP
 
   -- The data commands are only accepted if a PORT command is in scope
-  if cxt.dataServer == nil and cxt.dataSocket == nil then
+  if FTP.dataServer == nil and cxt.dataSocket == nil then
     return send("502 Active mode not supported. "..cmd.." not implemented")
   end
 
@@ -340,8 +352,8 @@ processDataCmds = function(cxt, cmd, arg)  -- upval: FTP (, pairs, file, tostrin
     end
     table.sort(nameList)
 
-    function cxt.getData() -- upval: cmd, fileSize, nameList (, table)
-      local list, user = {}, FTP.user
+    function cxt.getData(c) -- upval: cmd, fileSize, nameList
+      local list, user = {}, c.FTP.user
       for i = 1,10 do -- luacheck: no unused
         if #nameList == 0 then break end
         local f = table.remove(nameList, 1)
@@ -355,7 +367,7 @@ processDataCmds = function(cxt, cmd, arg)  -- upval: FTP (, pairs, file, tostrin
   elseif cmd == "RETR" then
     local f = file.open(arg, "r")
     if f then -- define a getter to read the file
-      function cxt.getData()     -- upval: f
+      function cxt.getData(c) -- luacheck: ignore c -- upval: f
         local buf = f:read(1024)
         if not buf then f:close(); f = nil; end
         return buf
@@ -365,13 +377,13 @@ processDataCmds = function(cxt, cmd, arg)  -- upval: FTP (, pairs, file, tostrin
   elseif cmd == "STOR" then
     local f = file.open(arg, "w")
     if f then -- define a setter to write the file
-      function cxt.setData(rec) -- upval f, arg (, debug)
-     -- debug("writing %u bytes to %s", #rec, arg)
+      function cxt.setData(c, rec) -- luacheck: ignore c -- upval: f (, arg)
+        cxt.debug("writing %u bytes to %s", #rec, arg)
         return f:write(rec)
       end -- cxt.saveData(rec)
-      function cxt.fileClose() -- upval cxt, f, arg (,debug)
-     -- debug("closing %s", arg)
-        f:close(); cxt.fileClose, f = nil, nil
+      function cxt.fileClose(c) -- luacheck: ignore c -- upval: f (,arg)
+        cxt.debug("closing %s", arg)
+        f:close(); f = nil
       end -- cxt.close()
     end
 
@@ -380,13 +392,12 @@ processDataCmds = function(cxt, cmd, arg)  -- upval: FTP (, pairs, file, tostrin
   send((cxt.getData or cxt.setData) and "150 Accepted data connection" or
                                         "451 Can't open/create "..arg)
   if cxt.getData and cxt.dataSocket then
-    debug ("poking sender to initiate first xfer")
-    post(function() cxt.sender(cxt.dataSocket) end)
+    cxt.debug ("poking sender to initiate first xfer")
+    node.task.post(function() cxt.sender(cxt.dataSocket) end)   -- upval: cxt
   end
 
-end -- processDataCmds(cmd, arg, send)
-
-
+end --[[SPLIT IGNORE]]
+function FTP.dataServer(...)  --[[SPLIT HERE ftp-dataServer]]
 ----------------------------- Data Port Routines -----------------------------
 -- These are used to manage the data transfer over the data port.  This is
 -- set up lazily either by a PASV or by the first LIST NLST RETR or STOR
@@ -394,62 +405,64 @@ end -- processDataCmds(cmd, arg, send)
 -- handle the actual xfer. Also note that the sending process can be primed in
 --
 ----------------   Open a new data server and port ---------------------------
-dataServer = function(cxt, n) -- upval: (pcall, net, ftpDataOpen, debug, tostring)
-  local dataSrv = cxt.dataServer
-  if dataSrv then -- close any existing listener
-    pcall(dataSrv.close, dataSrv)
-  end
+
+local cxt, n = ...
+
+  local dataSvr = cxt.dataServer
+  if dataSvr then pcall(dataSvr.close, dataSrv) end -- luacheck: ignore -- close any existing listener
   if n then
     -- Open a new listener if needed. Note that this is only used to establish
     -- a single connection, so ftpDataOpen closes the server socket
-    cxt.dataServer = net.createServer(net.TCP, 300)
-    cxt.dataServer:listen(n, function(sock) -- upval: cxt, (ftpDataOpen)
-      ftpDataOpen(cxt,sock)
+    dataSvr = net.createServer(net.TCP, 300)
+    cxt.dataServer = dataSvr
+    dataSvr:listen(n, function(sock) -- upval: cxt
+      cxt.FTP.ftpDataOpen(cxt,sock)
       end)
- -- debug("Listening on Data port %u, server %s",n, tostring(cxt.dataServer))
+    cxt.debug("Listening on Data port %u, server %s",n, tostring(cxt.dataServer))
   else
     cxt.dataServer = nil
- -- debug("Stopped listening on Data port",n)
+    cxt.debug("Stopped listening on Data port",n)
   end
-end -- dataServer(n)
 
+end --[[SPLIT IGNORE]]
+function FTP.ftpDataOpen(...)  --[[SPLIT HERE ftp-ftpDataOpen]]
 ----------------------- Connection on FTP data port ------------------------
-ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
+
+local cxt, dataSocket = ...
 
   local sport,sip = dataSocket:getaddr()
   local cport,cip = dataSocket:getpeer()
-  debug("Opened data socket %s from %s:%u to %s:%u", tostring(dataSocket),sip,sport,cip,cport )
+  cxt.debug("Opened data socket %s from %s:%u to %s:%u", tostring(dataSocket),sip,sport,cip,cport )
   cxt.dataSocket = dataSocket
 
   cxt.dataServer:close()
   cxt.dataServer = nil
 
-  local function cleardown(skt,type) -- upval: cxt (, debug, tostring, post, pcall)
-    -- luacheck: push no unused
-    type = type==1 and "disconnection" or "reconnection"
+  function cxt.cleardown(cxt, skt, cdtype)  --luacheck: ignore cxt -- shadowing
+    -- luacheck: ignore cdtype which
+    cdtype = cdtype==1 and "disconnection" or "reconnection"
     local which = cxt.setData and "setData" or (cxt.getData and cxt.getData or "neither")
-    --debug("Cleardown entered from %s with %s", type, which)
-    -- luacheck: pop
+    cxt.debug("Cleardown entered from %s with %s", cdtype, which)
     if cxt.setData then
-      cxt.fileClose()
+      cxt:fileClose()
       cxt.setData = nil
       cxt.send("226 Transfer complete.")
     else
       cxt.getData, cxt.sender = nil, nil
     end
- -- debug("Clearing down data socket %s", tostring(skt))
-    post(function() -- upval: skt, cxt, (, pcall)
-            pcall(skt.close, skt); skt=nil
-            cxt.dataSocket = nil
-         end)
+    cxt.debug("Clearing down data socket %s", tostring(skt))
+    node.task.post(function() -- upval: cxt, skt
+        pcall(skt.close, skt); skt=nil
+        cxt.dataSocket = nil
+      end)
   end
 
   local on_hold = false
 
-  dataSocket:on("receive", function(skt, rec) --upval: cxt, on_hold (, debug, tstring, post, node, pcall)
+  dataSocket:on("receive", function(skt, rec) -- upval: cxt, on_hold
 
-    local which = cxt.setData and "setData" or (cxt.getData and cxt.getData or "neither")-- luacheck: no unused
-    --debug("Received %u data bytes with %s", #rec, which)
+    local rectype = cxt.setData and "setData" or (cxt.getData and cxt.getData or "neither")
+    cxt.debug("Received %u data bytes with %s", #rec, rectype)
 
     if not cxt.setData then return end
 
@@ -458,33 +471,33 @@ ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
       -- large file. As soon as a record arrives assert a flow control hold.
       -- This can take up to 5 packets to come into effect at which point the
       -- low priority unhold task is executed releasing the flow again.
-   -- debug("Issuing hold on data socket %s", tostring(skt))
+      cxt.debug("Issuing hold on data socket %s", tostring(skt))
       skt:hold(); on_hold = true
-      post(node.task.LOW_PRIORITY,
-           function() -- upval: skt, on_hold (, debug, tostring))
-          -- debug("Issuing unhold on data socket %s", tostring(skt))
+      node.task.post(node.task.LOW_PRIORITY,
+           function() -- upval: skt, on_hold
+             cxt.debug("Issuing unhold on data socket %s", tostring(skt))
              pcall(skt.unhold, skt); on_hold = false
            end)
     end
 
-    if not cxt.setData(rec) then
-   -- debug("Error writing to SPIFFS")
-      cxt.fileClose()
+    if not cxt:setData(rec) then
+      cxt.debug("Error writing to SPIFFS")
+      cxt:fileClose()
       cxt.setData = nil
       cxt.send("552 Upload aborted. Exceeded storage allocation")
     end
   end)
 
-  function cxt.sender(skt) -- upval: cxt (, debug)
-    debug ("entering sender")
+  function cxt.sender(skt) -- upval: cxt
+    cxt.debug ("entering sender")
     if not cxt.getData then return end
     skt = skt or cxt.dataSocket
-    local rec = cxt.getData()
+    local rec = cxt:getData()
     if rec and #rec > 0 then
-   -- debug("Sending %u data bytes", #rec)
+      cxt.debug("Sending %u data bytes", #rec)
       skt:send(rec)
     else
-   -- debug("Send of data completed")
+      cxt.debug("Send of data completed")
       skt:close()
       cxt.send("226 Transfer complete.")
       cxt.getData, cxt.dataSocket = nil, nil
@@ -492,14 +505,11 @@ ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
   end
 
   dataSocket:on("sent", cxt.sender)
-  dataSocket:on("disconnection", function(skt) return cleardown(skt,1) end)
-  dataSocket:on("reconnection",  function(skt) return cleardown(skt,2) end)
+  dataSocket:on("disconnection", function(skt) return cxt:cleardown(skt,1) end) -- upval: cxt
+  dataSocket:on("reconnection",  function(skt) return cxt:cleardown(skt,2) end) -- upval: cxt
 
   -- if we are sending to client then kick off the first send
   if cxt.getData then cxt.sender(cxt.dataSocket) end
 
-end -- ftpDataOpen(socket)
-
------------------------------------------------- -----------------------------
-
-return FTP
+end --[[SPLIT HERE]]
+return FTP --[[SPLIT IGNORE]]

+ 3 - 0
lua_modules/telnet/README.md

@@ -0,0 +1,3 @@
+# Telnet Module
+
+Documentation for this Lua module is available in the [telnet.md](../../docs/lua-modules/telnet.md) file and in the [Official NodeMCU Documentation](https://nodemcu.readthedocs.io/) in `Lua Modules` section.

+ 3 - 2
lua_examples/telnet/telnet_pipe.lua → lua_modules/telnet/telnet.lua

@@ -1,3 +1,5 @@
+--[[SPLIT MODULE telnet]]
+
 --[[  A telnet server   T. Ellison,  June 2019
 
 This version of the telnet server demonstrates the use of the new stdin and stout
@@ -65,5 +67,4 @@ function M.close(this)
 end
 
 return M
-
-
+--[[SPLIT HERE]]

+ 1 - 0
mkdocs.yml

@@ -58,6 +58,7 @@ pages:
     - 'lm92': 'lua-modules/lm92.md'
     - 'mcp23008': 'lua-modules/mcp23008.md'
     - 'redis': 'lua-modules/redis.md'
+    - 'telnet': 'lua-modules/telnet.md'
     - 'yeelink': 'lua-modules/yeelink.md'
 - C Modules:
     - 'adc': 'modules/adc.md'