Browse Source

reorganize stuff

master
Wesley Kerfoot 2 years ago
parent
commit
d71060e77a
  1. BIN
      firmware/nodemcu-release-16-modules-2021-10-09-22-33-53-float.bin
  2. 7
      flash.sh
  3. 45
      libs/fifo.lua
  4. 134
      libs/fifosock.lua
  5. 145
      libs/fifosocktest.lua
  6. 217
      libs/httpserver.lua
  7. 56
      server.lua

BIN
firmware/nodemcu-release-16-modules-2021-10-09-22-33-53-float.bin

Binary file not shown.

7
flash.sh

@ -1,12 +1,11 @@
nodemcu-tool reset
esptool.py erase_flash
esptool.py --port /dev/ttyUSB0 write_flash -fm qio 0x00000 nodemcu-release-16-modules-2021-10-09-22-33-53-float.bin
esptool.py --port /dev/ttyUSB0 write_flash -fm qio 0x00000 firmware/nodemcu-release-16-modules-2021-10-09-22-33-53-float.bin
nodemcu-tool upload *.lua
nodemcu-tool upload *.lua libs/*
while [[ $? != 0 ]]; do
nodemcu-tool upload *.lua
nodemcu-tool upload *.lua libs/*
done
echo 'dofile("init.lua")' | nodemcu-tool terminal

45
libs/fifo.lua

@ -0,0 +1,45 @@
-- A generic fifo module. See docs/lua-modules/fifo.md for use examples.
local tr, ti = table.remove, table.insert
-- Remove an element and pass it to k, together with a boolean indicating that
-- this is the last element in the queue; if that returns a value, leave that
-- pending at the top of the fifo.
--
-- If k returns nil, the fifo will be advanced. Moreover, k may return a
-- second result, a boolean, indicating "phantasmic" nature of this element.
-- If this boolean is true, then the fifo will advance again, passing the next
-- value, if there is one, to k, or priming itself for immediate execution at
-- the next call to queue.
--
-- If the queue is empty, do not invoke k but flag it to enable immediate
-- execution at the next call to queue.
--
-- Returns 'true' if the queue contained at least one non-phantom entry,
-- 'false' otherwise.
local function dequeue(q,k)
if #q > 0
then
local new, again = k(q[1], #q == 1)
if new == nil
then tr(q,1)
if again then return dequeue(q, k) end -- note tail call
else q[1] = new
end
return true
else q._go = true ; return false
end
end
-- Queue a on queue q and dequeue with `k` if the fifo had previously emptied.
local function queue(q,a,k)
ti(q,a)
if k ~= nil and q._go then q._go = false; dequeue(q, k) end
end
-- return a table containing just the FIFO constructor
return {
['new'] = function()
return { ['_go'] = true ; ['queue'] = queue ; ['dequeue'] = dequeue }
end
}

134
libs/fifosock.lua

@ -0,0 +1,134 @@
-- Wrap a two-staged fifo around a socket's send; see
-- docs/lua-modules/fifosock.lua for more documentation.
--
-- See fifosocktest.lua for some examples of use or tricky cases.
--
-- Our fifos can take functions; these can be useful for either lazy
-- generators or callbacks for parts of the stream having been sent.
local BIGTHRESH = 256 -- how big is a "big" string?
local SPLITSLOP = 16 -- any slop in the big question?
local FSMALLLIM = 32 -- maximum number of small strings held
local COALIMIT = 3
local concat = table.concat
local insert = table.insert
local gc = collectgarbage
local function wrap(sock)
-- the two fifos
local fsmall, lsmall, fbig = {}, 0, (require "fifo").new()
-- ssend last aggregation string and aggregate count
local ssla, sslan = nil, 0
local ssend = function(s,islast)
local ns = nil
-- Optimistically, try coalescing FIFO dequeues. But, don't try to
-- coalesce function outputs, since functions might be staging their
-- execution on the send event implied by being called.
if type(s) == "function" then
if sslan ~= 0 then
sock:send(ssla)
ssla, sslan = nil, 0; gc()
return s, false -- stay as is and wait for :on("sent")
end
s, ns = s()
elseif type(s) == "string" and sslan < COALIMIT then
if sslan == 0
then ssla, sslan = s, 1
else ssla, sslan = ssla .. s, sslan + 1
end
if islast then
-- this is shipping; if there's room, steal the small fifo, too
if sslan < COALIMIT then
sock:send(ssla .. concat(fsmall))
fsmall, lsmall = {}, 0
else
sock:send(ssla)
end
ssla, sslan = "", 0; gc()
return nil, false
else
return nil, true
end
end
-- Either that was a function or we've hit our coalescing limit or
-- we didn't ship above. Ship now, if there's something to ship.
if s ~= nil then
if sslan == 0 then sock:send(s) else sock:send(ssla .. s) end
ssla, sslan = nil, 0; gc()
return ns or nil, false
elseif sslan ~= 0 then
assert (ns == nil)
sock:send(ssla)
ssla, sslan = nil, 0; gc()
return nil, false
else
assert (ns == nil)
return nil, true
end
end
-- Move fsmall to fbig; might send if fbig empty
local function promote(f)
if #fsmall == 0 then return end
local str = concat(fsmall)
fsmall, lsmall = {}, 0
fbig:queue(str, f or ssend)
end
local function sendnext()
if not fbig:dequeue(ssend) then promote() end
end
sock:on("sent", sendnext)
return function(s)
-- don't sweat the petty things
if s == nil or s == "" then return end
-- Function? Go ahead and queue this thing in the right place.
if type(s) == "function" then promote(); fbig:queue(s, ssend); return; end
s = tostring(s)
-- cork sending until the end in case we're the head of line
local corked = false
local function corker(t) corked = true; return t end
-- small fifo would overfill? promote it
if lsmall + #s > BIGTHRESH or #fsmall >= FSMALLLIM then promote(corker) end
-- big string? chunk and queue big components immediately
-- behind any promotion that just took place
while #s > BIGTHRESH + SPLITSLOP do
local pfx
pfx, s = s:sub(1,BIGTHRESH), s:sub(BIGTHRESH+1)
fbig:queue(pfx, corker)
end
-- Big string? queue and maybe tx now
if #s > BIGTHRESH then fbig:queue(s, corker)
-- small and fifo in immediate dequeue mode
elseif fbig._go and lsmall == 0 then fbig:queue(s, corker)
-- small and queue already moving; let it linger in the small fifo
else insert(fsmall, s) ; lsmall = lsmall + #s
end
-- if it happened that we corked the transmission above...
-- if we queued a good amount of data, go ahead and start transmitting;
-- otherwise, wait a tick and hopefully we will queue more in the interim
-- before transmitting.
if corked then
if #fbig <= COALIMIT
then tmr.create():alarm(1, tmr.ALARM_SINGLE, sendnext)
else sendnext()
end
end
end
end
return { wrap = wrap }

145
libs/fifosocktest.lua

@ -0,0 +1,145 @@
--
-- Set verbose to 0 for quiet output (either the first assertion failure or
-- "All tests OK"), to 1 to see the events ("SEND", "SENT", "CHECK") without
-- the actual bytes, or to 2 to see the events with the bytes.
--
local verbose = 0
local vprint = (verbose > 0) and print or function() end
--
-- Mock up enough of the nodemcu tmr structure, but pretend that nothing
-- happens between ticks. This won't exercise the optimistic corking logic,
-- but that's probably fine.
-- luacheck: push ignore
tmr = {}
tmr.ALARM_SINGLE = 0
function tmr.create()
local r = {}
function r:alarm(_i, _t, cb) vprint("TMR") cb() end
return r
end
-- luacheck: pop
--
-- Mock up enough of the nodemcu net.socket type; have it log all the sends
-- into this "outs" array so that we can later check against it.
--
local outs = {}
local fakesock = {
cb = nil,
on = function(this, _, cb) this.cb = cb end,
send = function(this, s) vprint("SEND", (verbose > 1) and s) table.insert(outs, s) end -- luacheck: no unused
}
local function sent() vprint("SENT") fakesock.cb() end
-- And wrap a fifosock around this fake socket
local fsend = (require "fifosock").wrap(fakesock)
-- Verify that the next unconsumed output is as indicated
local function fcheck(x)
vprint ("CHECK", (verbose > 1) and x)
assert (#outs > 0)
assert (x == outs[1])
table.remove(outs, 1)
end
-- Enqueue an empty function to prevent coalescing within the fifosock
local function nocoal() fsend(function() return nil end) end
-- Send and check, for when the string should be sent exactly as is
local function fsendc(x) fsend(x) fcheck(x) end
-- Check that there are no more outputs
local function fchecke() vprint("CHECKE") assert (#outs == 0) end
--
-- And now for the tests, which start easy and grow in complexity
--
fsendc("abracadabra none")
sent() ; fchecke()
fsendc("abracadabra three")
fsend("short")
fsend("string")
fsend("build")
sent() ; fcheck("shortstringbuild")
sent() ; fchecke()
-- Hit default FSMALLLIM while building up
fsendc("abracadabra lots small")
for i = 1, 32 do fsend("a") end -- luacheck: no unused
nocoal()
for i = 1, 4 do fsend("a") end -- luacheck: no unused
sent() ; fcheck(string.rep("a", 32))
sent() ; fcheck(string.rep("a", 4))
sent() ; fchecke()
-- Hit string length while building up
fsendc("abracadabra overlong")
for i = 1, 10 do fsend(string.rep("a",32)) end -- luacheck: no unused
sent() ; fcheck(string.rep("a", 320))
sent() ; fchecke()
-- Hit neither before sending a big string
fsendc("abracadabra mid long")
for i = 1, 6 do fsend(string.rep("a",32)) end -- luacheck: no unused
fsend(string.rep("b", 256))
nocoal()
for i = 1, 6 do fsend(string.rep("c",32)) end -- luacheck: no unused
sent() ; fcheck(string.rep("a", 192) .. string.rep("b", 256))
sent() ; fcheck(string.rep("c", 192))
sent() ; fchecke()
-- send a huge string, verify that it coalesces
fsendc(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 260))
sent() ; fchecke()
-- send a huge string, verify that it coalesces save for the short bit at the end
fsend(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 256) .. string.rep("d",256))
fsend("e")
fcheck(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 256))
sent() ; fcheck(string.rep("d",256) .. "e")
sent() ; fchecke()
-- send enough that our 4x lookahead still leaves something in the queue
fsend(string.rep("a",512) .. string.rep("b", 512) .. string.rep("c", 512))
fcheck(string.rep("a",512) .. string.rep("b", 512))
sent() ; fcheck(string.rep("c",512))
sent() ; fchecke()
-- test a lazy generator
do
local ix = 0
local function gen() vprint("GEN", ix); ix = ix + 1; return ("a" .. ix), ix < 3 and gen end
fsend(gen)
fsend("b")
fcheck("a1")
sent() ; fcheck("a2")
sent() ; fcheck("a3")
sent() ; fcheck("b")
sent() ; fchecke()
end
-- test a completion-like callback that does send text
do
local ix = 0
local function gen() vprint("GEN"); ix = 1; return "efgh", nil end
fsend("abcd"); fsend(gen); fsend("ijkl")
assert (ix == 0)
fcheck("abcd"); assert (ix == 0)
sent() ; fcheck("efgh"); assert (ix == 1); ix = 0
sent() ; fcheck("ijkl"); assert (ix == 0)
sent() ; fchecke()
end
-- and one that doesn't
do
local ix = 0
local function gen() vprint("GEN"); ix = 1; return nil, nil end
fsend("abcd"); fsend(gen); fsend("ijkl")
assert (ix == 0)
fcheck("abcd"); assert (ix == 0)
sent() ; fcheck("ijkl"); assert (ix == 1); ix = 0
sent() ; fchecke() ; assert (ix == 0)
end
print("All tests OK")

217
libs/httpserver.lua

@ -0,0 +1,217 @@
------------------------------------------------------------------------------
-- HTTP server module
--
-- LICENCE: http://opensource.org/licenses/MIT
-- Vladimir Dronnikov <dronnikov@gmail.com>
------------------------------------------------------------------------------
local collectgarbage, tonumber, tostring = collectgarbage, tonumber, tostring
local http
do
------------------------------------------------------------------------------
-- request methods
------------------------------------------------------------------------------
local make_req = function(conn, method, url)
return {
conn = conn,
method = method,
url = url,
}
end
------------------------------------------------------------------------------
-- response methods
------------------------------------------------------------------------------
local make_res = function(csend, cfini)
local send = function(self, data, status)
-- TODO: req.send should take care of response headers!
if self.send_header then
csend("HTTP/1.1 ")
csend(tostring(status or 200))
-- TODO: real HTTP status code/name table
csend(" OK\r\n")
-- we use chunked transfer encoding, to not deal with Content-Length:
-- response header
self:send_header("Transfer-Encoding", "chunked")
-- TODO: send standard response headers, such as Server:, Date:
end
if data then
-- NB: no headers allowed after response body started
if self.send_header then
self.send_header = nil
-- end response headers
csend("\r\n")
end
-- chunked transfer encoding
csend(("%X\r\n"):format(#data))
csend(data)
csend("\r\n")
end
end
local send_header = function(_, name, value)
-- NB: quite a naive implementation
csend(name)
csend(": ")
csend(value)
csend("\r\n")
end
-- finalize request, optionally sending data
local finish = function(self, data, status)
-- NB: res.send takes care of response headers
if data then
self:send(data, status)
end
-- finalize chunked transfer encoding
csend("0\r\n\r\n")
-- close connection
cfini()
end
--
local res = { }
res.send_header = send_header
res.send = send
res.finish = finish
return res
end
------------------------------------------------------------------------------
-- HTTP parser
------------------------------------------------------------------------------
local http_handler = function(handler)
return function(conn)
local csend = (require "fifosock").wrap(conn)
local req, res
local buf = ""
local method, url
local ondisconnect = function(connection)
connection:on("receive", nil)
connection:on("disconnection", nil)
connection:on("sent", nil)
collectgarbage("collect")
end
local cfini = function()
csend(function()
conn:on("sent", nil)
conn:close()
ondisconnect(conn)
end)
end
-- header parser
local cnt_len = 0
local onheader = function(_, k, v)
-- TODO: look for Content-Type: header
-- to help parse body
-- parse content length to know body length
if k == "content-length" then
cnt_len = tonumber(v)
end
if k == "expect" and v == "100-continue" then
csend("HTTP/1.1 100 Continue\r\n")
end
-- delegate to request object
if req and req.onheader then
req:onheader(k, v)
end
end
-- body data handler
local body_len = 0
local ondata = function(_, chunk)
-- feed request data to request handler
if not req or not req.ondata then return end
req:ondata(chunk)
-- NB: once length of seen chunks equals Content-Length:
-- ondata(conn) is called
body_len = body_len + #chunk
-- print("-B", #chunk, body_len, cnt_len, node.heap())
if body_len >= cnt_len then
req:ondata()
end
end
local onreceive = function(connection, chunk)
-- merge chunks in buffer
if buf then
buf = buf .. chunk
else
buf = chunk
end
-- consume buffer line by line
while #buf > 0 do
-- extract line
local e = buf:find("\r\n", 1, true)
if not e then break end
local line = buf:sub(1, e - 1)
buf = buf:sub(e + 2)
-- method, url?
if not method then
do
local _
-- NB: just version 1.1 assumed
_, _, method, url = line:find("^([A-Z]+) (.-) HTTP/1.1$")
end
if method then
-- make request and response objects
req = make_req(connection, method, url)
res = make_res(csend, cfini)
end
-- spawn request handler
handler(req, res)
-- header line?
elseif #line > 0 then
-- parse header
local _, _, k, v = line:find("^([%w-]+):%s*(.+)")
-- header seems ok?
if k then
k = k:lower()
onheader(connection, k, v)
end
-- headers end
else
-- NB: we explicitly reassign receive handler so that
-- next received chunks go directly to body handler
connection:on("receive", ondata)
-- NB: we feed the rest of the buffer as starting chunk of body
ondata(connection, buf)
-- buffer no longer needed
buf = nil
-- parser done
break
end
end
end
conn:on("receive", onreceive)
conn:on("disconnection", ondisconnect)
end
end
------------------------------------------------------------------------------
-- HTTP server
------------------------------------------------------------------------------
local srv
local createServer = function(port, handler)
-- NB: only one server at a time
if srv then srv:close() end
srv = net.createServer(net.TCP, 15)
-- listen
srv:listen(port, http_handler(handler))
return srv
end
------------------------------------------------------------------------------
-- HTTP server methods
------------------------------------------------------------------------------
http = {
createServer = createServer,
}
end
return http

56
server.lua

@ -1,56 +0,0 @@
temperature = "0"
require("httpserver").createServer(80, function(req, res)
-- analyse method and url
print("+R", req.method, req.url, node.heap())
-- setup handler of headers, if any
req.onheader = function(self, name, value) -- luacheck: ignore
print("+H", name, value)
-- E.g. look for "content-type" header,
-- setup body parser to particular format
-- if name == "content-type" then
-- if value == "application/json" then
-- req.ondata = function(self, chunk) ... end
-- elseif value == "application/x-www-form-urlencoded" then
-- req.ondata = function(self, chunk) ... end
-- end
-- end
end
-- setup handler of body, if any
req.ondata = function(self, chunk) -- luacheck: ignore
print("+B", chunk and #chunk, node.heap())
--if not chunk then
-- reply
res:send(nil, 200)
-- res:send_header("Connection", "close")
-- res:send("Hello, world!\n")
--res:finish("yolo")
--end
res:finish(temperature .. "\n")
end
-- or just do something not waiting till body (if any) comes
--res:finish("Hello, world!")
--res:finish("Salut, monde!")
end)
print("Starting up")
for k, v in pairs(softuart) do
print(tostring(k) .. ", " .. tostring(v))
end
if s then
print("Configuring callback")
s:on("data", "\n",
function(data)
stripped = string.gsub(data, '%s+', '')
print("Temperature = " .. stripped)
s:write("Sending back to cp: \n")
print("Wrote stuff back")
temperature = stripped
if data == "quit" then
s:on("data") -- unregister callback function
end
end, 0)
end
Loading…
Cancel
Save