]> hydra-www.ietfng.org Git - acmetensortoys-esp-lua_core/commitdiff
Initial checkin of framework and lamp example
authorNathaniel Wesley Filardo <nwfilardo@gmail.com>
Fri, 7 Oct 2016 03:27:43 +0000 (23:27 -0400)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Fri, 7 Oct 2016 17:20:32 +0000 (13:20 -0400)
17 files changed:
cap1188/cap1188-init.lua [new file with mode: 0644]
cap1188/cap1188.lua [new file with mode: 0644]
host/pushinit.sh [new file with mode: 0755]
host/pushvia.expect [new file with mode: 0755]
init.lua [new file with mode: 0644]
net/nwfmqtt.lua [new file with mode: 0644]
net/nwfnet-diag.lua [new file with mode: 0644]
net/nwfnet-go.lua [new file with mode: 0644]
net/nwfnet-sntp.lua [new file with mode: 0644]
net/nwfnet.lua [new file with mode: 0644]
telnetd/telnetd-diag.lua [new file with mode: 0644]
telnetd/telnetd-file.lua [new file with mode: 0644]
telnetd/telnetd.lua [new file with mode: 0644]
tq/tq-diag.lua [new file with mode: 0644]
tq/tq.lua [new file with mode: 0644]
util/diag.lua [new file with mode: 0644]
util/fifosock.lua [new file with mode: 0644]

diff --git a/cap1188/cap1188-init.lua b/cap1188/cap1188-init.lua
new file mode 100644 (file)
index 0000000..169c15b
--- /dev/null
@@ -0,0 +1,27 @@
+return {
+  ["init"] = function(trigpin,resetpin,gofn)
+    gpio.mode(trigpin,gpio.INT,gpio.PULLUP)   -- GPIO12 is cap sensor IRQ (active low)
+    gpio.mode(resetpin,gpio.OUTPUT,gpio.FLOAT) -- GPIO14 is cap sensor reset (active low)
+  
+    -- put cap through a reset cycle and then hook our IRQ handler
+    gpio.trig(trigpin)
+    gpio.write(resetpin,gpio.HIGH)
+    tq:queue(200,function()
+      gpio.write(resetpin,gpio.LOW)
+      tq:queue(300,function()
+        print('CAP', cap:info())
+        gpio.trig(trigpin, "low", gofn)
+        cap:wr(0x20,0x28) -- config: add maximum duration autorecalibrate
+        cap:wr(0x22,0xF4) -- raise maximum duration to 11 seconds; repeat every 175ms
+        cap:wr(0x2A,0x00) -- do not block multiple touches
+        cap:wr(0x41,0x30) -- change standby sampling rate
+        cap:wr(0x72,0xFF) -- link all LEDs to touches
+      end)
+    end)
+  end,
+
+  -- calibrate all input sensors
+  ["calibrate"] = function() 
+     cap:wr(0x26,0xFF)
+  end
+}
diff --git a/cap1188/cap1188.lua b/cap1188/cap1188.lua
new file mode 100644 (file)
index 0000000..5893c63
--- /dev/null
@@ -0,0 +1,40 @@
+-- DEPENDS: i2c
+-- cap1188 based heavily on adafruit's documentation and code as well as the
+-- device datasheet.  Unsurprising, really, esp. that it's their breakout.
+local self = {}
+self.addr = 0x29
+
+function self:rr(r)
+  i2c.start(0)
+  if not i2c.address(0, self.addr, i2c.TRANSMITTER) then i2c.stop(0) return nil end
+   i2c.write(0,r)
+  i2c.start(0)
+   i2c.address(0,0x29,i2c.RECEIVER)
+   local x = i2c.read(0,1)
+  i2c.stop(0)
+  return x:byte(1)
+end
+
+function self:wr(r,v)
+  i2c.start(0)
+  if not i2c.address(0, self.addr, i2c.TRANSMITTER) then i2c.stop(0) return nil end
+  i2c.write(0,r)
+  i2c.write(0,v)
+  i2c.stop(0)
+  return true
+end
+
+function self:mr(r,f) local n = f(self:rr(r)) ; self:wr(r,n); return n end
+
+function self:rt()
+  local t = self:rr(0x3)
+  self:mr(0, function(st) return bit.band(st,0xFE) end)
+  local t2 = self:rr(0x3)
+  return t, t2
+end
+
+function self:info()
+  return self:rr(0xFD), self:rr(0xFE), self:rr(0xFF)
+end
+
+return self
diff --git a/host/pushinit.sh b/host/pushinit.sh
new file mode 100755 (executable)
index 0000000..a48c051
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+set -e -u
+
+if [ -z ${HOST:-} ]; then
+  # Uses LUATOOL to push init and dependencies to the device; bootstrap!
+  PUSHCMD="${LUATOOL} --delay 0.1 -p ${MCUPORT} -b ${MCUBAUD}"
+  dopush() { ${PUSHCMD} -f $1 -t ${2:-`basename $1`}; }
+  dopushcompile() { ${PUSHCMD} -f $1 -t ${2:-`basename $1`} -c; }
+else
+  # Uses host/pushvia to push everything if HOST is set
+  PUSHCMD="./host/pushvia.expect ${HOST} ${PORT:-23}"
+  dopush() { ${PUSHCMD} ${2:-`basename $1`} $1; }
+  dopushcompile() { ${PUSHCMD} ${2:-`basename $1`} $1 compile; }
+fi
+
+dopushcompile util/diag.lua
+dopushcompile tq/tq.lua
+dopushcompile tq/tq-diag.lua
+dopushcompile net/nwfnet.lua
+dopushcompile net/nwfnet-sntp.lua
+dopushcompile net/nwfnet-go.lua
+dopushcompile net/nwfnet-diag.lua
+dopush        net/conf/nwfnet.conf
+dopush        net/conf/nwfnet.cert
+dopush        net/conf/nwfnet.conf2
+dopushcompile util/fifosock.lua
+dopushcompile telnetd/telnetd.lua
+dopushcompile telnetd/telnetd-file.lua
+dopushcompile telnetd/telnetd-diag.lua
+dopush        init.lua
+
+echo "SUCCESS"
diff --git a/host/pushvia.expect b/host/pushvia.expect
new file mode 100755 (executable)
index 0000000..981d43b
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env expect
+
+# expect wrapper around nc to push a file via telnetd-file.lua's interface;
+# invoke with "host port remote_file_name local_file_name [compile]"
+
+package require Tcl 8
+package require base64 "2.4.2"
+
+proc docmd {sid cmd} {
+  set timeout 5
+
+  send -i ${sid} ${cmd}
+  expect {
+    -i ${sid} -ex "\n$ " {}
+    -i ${sid} -ex "ERR: " {
+      send_user "Command error\n"
+      exit 1
+    }
+    -i ${sid} eof {
+      send_user "Command EOF\n"
+      exit 1
+    }
+    timeout {
+      send_user "Command timeout\n"
+      exit 1
+    }
+  }
+
+}
+
+spawn {*}"nc [lindex $argv 0] [lindex $argv 1]"
+set remote_sid ${spawn_id}
+
+set rfn [lindex $argv 2]
+set lfn [lindex $argv 3]
+set lchan [open ${lfn} r+]
+fconfigure ${lchan} -translation binary -encoding binary
+
+set timeout 2
+expect {
+  -i ${remote_sid} -ex "\n$ " {}
+  timeout {
+    send_user "Failed to find initial prompt"
+    exit 1
+  }
+}
+
+docmd ${remote_sid} "file remove ${rfn}\n"
+
+set pos 0
+while 1 {
+  set chunk [read ${lchan} 128]
+  set length [string length ${chunk}]
+  if {${length} <= 0} { break }
+
+  set echunk [::base64::encode -maxlen 0 ${chunk}]
+  docmd ${remote_sid} "file pwrite ${pos} ${rfn} ${echunk}\n"
+
+  set pos [expr {$pos + $length}]
+}
+
+if { "compile" == [lindex $argv 4] } {
+  docmd ${remote_sid} "file compile ${rfn}\n"
+  docmd ${remote_sid} "file remove ${rfn}\n"
+}
+
+send -i ${remote_sid} "quit\n"
+expect { -i ${remote_sid} eof {} }
diff --git a/init.lua b/init.lua
new file mode 100644 (file)
index 0000000..0f34362
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,38 @@
+-- DEPEND: gpio, node, rtctime?, tmr ; nwfnet, nwfnet-diag, nwfnet-go, telnetd
+if rtctime then rtctime.set(0) end -- set time to 0 until someone corrects us
+local function goab()
+       dofile("nwfnet-diag.lc")(true)
+       dofile("diag.lc")
+       dofile("nwfnet-go.lc")
+       tcpserv = net.createServer(net.TCP, 180)
+        tcpserv:listen(23,function(k)
+          local telnetd = dofile "telnetd.lc"
+         telnetd.on["conn"] = function(k)
+            tmr.unregister(6)
+            k:send(string.format("NODE-%06X RECOVERY (auto reboot cancelled)",node.chipid())) end
+          telnetd.server(k)
+        end)
+end
+local function go(fn) 
+       local f, e = loadfile(fn)
+       if f == nil then print("Error:",fn,e); goab() else node.task.post(f) end
+end
+local function goi2() go("init2.lc") end
+local function waitFLASH()
+       local function stop_() gpio.mode(3,gpio.INPUT); gpio.trig(3); tmr.unregister(6); stop = nil end
+        function stop() stop_(); goab(); end
+       print("Reset delay!  Bounce GPIO3 low or type 'stop()' to stop autoboot...")
+       gpio.mode(3,gpio.INT,gpio.PULLUP)
+       tmr.alarm(6,8000,tmr.ALARM_SINGLE,function() print("Continuing boot..."); stop_(); goi2() end)
+       gpio.trig(3,"low",function(_) print("Aborting..."); stop() end)
+end
+local function bootPANIC()
+  print("Panic!  Lingering for five minutes with telnet console up; 'tmr.unregister(6)' to persist...")
+  tmr.alarm(6,300000,tmr.ALARM_SINGLE,node.restart)
+  goab()
+end
+local bct = { [0] = waitFLASH, [1] = bootPANIC, [2] = bootPANIC, [3] = bootPANIC, [4] = waitFLASH, [5] = goi2, [6] = waitFLASH }
+local mt = {__index = function() return goi2 end}
+setmetatable(bct,mt)
+local _, bc = node.bootreason()
+bct[bc]()
diff --git a/net/nwfmqtt.lua b/net/nwfmqtt.lua
new file mode 100644 (file)
index 0000000..07ae8fb
--- /dev/null
@@ -0,0 +1,50 @@
+-- DEPENDS: cjson, file, mqtt; nwfnet
+local nwfnet = require "nwfnet"
+local self = {}
+function self.mkclient(cf) -- construct a client with config from json file cf
+  if file.open(cf) then
+    local conf = cjson.decode(file.read())
+    local c, k, u, p
+    if type(conf) == "table" then
+      c = conf["clientid"]; k = conf["keepalive"]; u = conf["user"]; p = conf["pass"]
+    end
+    c = c or string.format("NODE-%06X",node.chipid())
+    k = k or 1500
+    file.close()
+    local m = mqtt.Client(c,k,u,p)
+    m:on("connect", function(c) nwfnet:runnet("mqttconn",c) end)
+    m:on("offline", function(c) nwfnet:runnet("mqttdscn",c) end)
+    m:on("message", function(c,t,m) nwfnet:runmqtt(c,t,m) end)
+    return m, u, c
+  end
+end
+function self.connect(m,cf) -- make a connection with parameters from json file cf
+  local broker, port, secure
+  if file.open(cf) then
+    local conf = cjson.decode(file.read())
+    if type(conf) == "table" then
+      broker = conf["broker"]; port = conf["port"]; secure = conf["secure"]
+    end
+    file.close()
+  end
+  conf = nil
+  broker = broker or "iot.eclipse.org"
+  port = port or 1883
+  secure = (secure == 1) or 0
+  return m:connect(broker,port,secure,0)
+end
+function self.heartbeat(m,topic,tq,period) -- set up lw&t and periodically heartbeat using tq until cancelled
+  m:lwt(topic,"dead",1,1)
+  local handle
+  local function beat() mqc:publish(topic,"beat",1,1); handle = tq:queue(period, beat) end
+  handle = tq:queue(period,beat)
+  return function() tq:dequeue(handle) end
+end
+function self.suball(m,fn) -- subscribe to all lines in a file
+  if file.open(fn) then
+    local line
+    for line in function() return file.readline() end do m:subscribe(line:sub(1,-2),1) end
+    file.close()
+  end
+end
+return self
diff --git a/net/nwfnet-diag.lua b/net/nwfnet-diag.lua
new file mode 100644 (file)
index 0000000..08f03e6
--- /dev/null
@@ -0,0 +1,16 @@
+-- DEPENDS: ; nwfnet
+return function(ena)
+  local nn = require "nwfnet"
+  if ena then
+    nn.onmqtt["diag"] = function(...) print('mqttmsg',...) end
+    nn.onnet["diag"] = function(e,...)
+      if     e == "wstagoip" then local t = ... ; print(e,t.IP,t.netmask,t.gateway)
+      elseif e == "wstaconn" then local t = ... ; print(e,t.SSID,t.BSSID,t.channel)
+      elseif e == "wstadscn" then local t = ... ; print(e,t.SSID,t.BSSID,t.reason)
+      else print(e,...) end
+    end
+  else
+    nn.onmqtt["diag"] = nil
+    nn.onnet["diag"] = nil
+  end
+end
diff --git a/net/nwfnet-go.lua b/net/nwfnet-go.lua
new file mode 100644 (file)
index 0000000..962c142
--- /dev/null
@@ -0,0 +1,55 @@
+-- DEPENDS: cjson, file, mdns, net, rtctime, sntp, wifi; nwfnet, nwfnet-sntp
+wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(t)
+  (require "nwfnet"):runnet("wstagoip",t)
+  mdns.register(wifi.sta.gethostname())
+  dofile("nwfnet-sntp.lc").dosntp(nil)
+end)
+wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, function(_) (require "nwfnet"):runnet("wstadtmo") end)
+wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(t) (require "nwfnet"):runnet("wstaconn",t) end)
+wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(t) (require "nwfnet"):runnet("wstadscn",t) end)
+
+-- One-shot configuration options; useful to change many things at once
+-- at the next boot; all of these options are persisted by the ESP
+if file.open("nwfnet.conf","r") then
+  local conf = cjson.decode(file.read())
+  if type(conf) == "table" then
+    local essid = conf["sta_essid"]; local pw = conf["sta_pw"]
+    if essid ~= nil and pw ~= nil then wifi.sta.config(essid,pw,0) end
+
+    if conf["ap"] ~= nil then pcall(wifi.ap.config,conf["ap"]) end
+
+    local modestr = conf["wifi_mode"]
+    if     modestr == "station"   then wifi.setmode(wifi.STATION)
+    elseif modestr == "softap"    then wifi.setmode(wifi.SOFTAP)
+    elseif modestr == "stationap" then wifi.setmode(wifi.STATIONAP)
+    else                               wifi.setmode(wifi.STATION)
+    end
+
+    print("Applied settings from nwfnet.conf; likely, you want to remove this file...")
+   else print("nwfnet.conf malformed")
+  end
+  file.close()
+end
+-- must come after we've got our event callbacks registered, yeah?
+wifi.sta.connect()
+
+if file.open("nwfnet.cert","r") then
+  local cert = ""
+  local chunk = file.read()
+  while chunk ~= nil do cert = cert..chunk; chunk = file.read() end
+  ok, res = pcall(net.cert.verify,cert)
+  file.close()
+  if ok then
+    print("Loaded cert from nwfnet.cert; likely, you want to remove this file...")
+  else 
+    print("Failed to load from nwfnet.cert", res)
+  end
+end
+
+if file.open("nwfnet.conf2","r") then
+  local conf = cjson.decode(file.read())
+  if type(conf) == "table" then
+    if conf["verify"] == 1 then print("Enabling certificate verification"); pcall(net.cert.verify,true) end
+   else print("nwfnet.conf2 malformed")
+  end
+end
diff --git a/net/nwfnet-sntp.lua b/net/nwfnet-sntp.lua
new file mode 100644 (file)
index 0000000..8adcd03
--- /dev/null
@@ -0,0 +1,28 @@
+local function dosntp(server)
+  local nn = require "nwfnet"
+  if not server then
+    if file.open("nwfnet.conf2","r") then
+      local conf = cjson.decode(file.read())
+      if type(conf) == "table" then
+        if conf["sntp"]   then print("Setting SNTP server"); server = conf["sntp"] end
+       else print("nwfnet.conf2 malformed")
+  end end end
+  local x, y
+  if not server then x, y, server = wifi.ap.getip() end
+  if not server then x, y, server = wifi.sta.getip() end
+  if not server then nn:runnet("sntperr", "No sntp server?") return end
+  sntp.sync(server,
+     function(sec,usec,server) rtctime.set(sec,usec); nn:runnet("sntpsync",sec,usec,server) end,
+     function(err) nn:runnet("sntperr",err) end
+  )
+end
+
+local function loopsntp(tq,period,server)
+  local function f() dosntp(server); tq:queue(period,f) end
+  tq:queue(period,f)
+end
+
+local self = {}
+self.dosntp = dosntp
+self.loopsntp = loopsntp
+return self
diff --git a/net/nwfnet.lua b/net/nwfnet.lua
new file mode 100644 (file)
index 0000000..f338815
--- /dev/null
@@ -0,0 +1,9 @@
+-- Just callback registries
+local nwfnet   = {}
+  -- possible events: wstaconn, wstagoip, wstadscn, wstadtmo; sntpsync, sntperr ; mqttconn, mqttdscn
+nwfnet.onnet   = {}
+  -- specifically mqtt message events
+nwfnet.onmqtt  = {}
+function nwfnet:runnet(e,...) for _,v in pairs(nwfnet.onnet) do v(e,...) end end
+function nwfnet:runmqtt(...) for _,v in pairs(nwfnet.onmqtt) do v(...) end end
+return nwfnet
diff --git a/telnetd/telnetd-diag.lua b/telnetd/telnetd-diag.lua
new file mode 100644 (file)
index 0000000..faceeb9
--- /dev/null
@@ -0,0 +1,11 @@
+-- DEPENDS: node, rtcfifo?
+return {
+  ["boot"] = function(_,s) s(string.format("raw=%d reason=%d",node.bootreason())) end,
+  ["info"] = function(_,s) s(string.format("major=%d minor=%d dev=%d chip=%d flash=%d fs=%d fm=%d fs=%d",node.info())) end,
+  ["heap"] = function(_,s) s(string.format("free=%d",node.heap())) end,
+  ["fifo"] = function(_,s) if rctfifo and rtcfifo.ready() ~= 0 then s(string.format("fifo=%d",rtcfifo.count())) else s("no rtcfifo") end end,
+  ["exec"] = function(l,s)
+       local f, err = loadstring(l)
+       if f then local ok, res = pcall(f); if ok == true then s("ok: "..tostring(res)) else s("pcall err: "..res) end
+         else s("err: "..err) end end
+}
diff --git a/telnetd/telnetd-file.lua b/telnetd/telnetd-file.lua
new file mode 100644 (file)
index 0000000..9841e70
--- /dev/null
@@ -0,0 +1,41 @@
+-- DEPENDS: encoder, file, net
+local function withfat(s,m,fn,off,k)
+    if not file.open(fn,m) then s("ERR: Cannot open file "..fn.." "..m); return end
+    if file.seek("set", tonumber(off)) == nil then s("ERR: Cannot seek file"); file.close(); return end
+    k()
+    file.close()
+end
+return {
+  ["info"] = function(ll,s) local rem, use, _ = file.fsinfo(); s(string.format("use=%d rem=%d",use,rem)) end
+, ["list"] = function(ll,s) for k,v in pairs(file.list()) do s(string.format("%s %d\n",k,v)) end end
+, ["remove"]  = function(ll,s) local fn = string.match(ll,"^%s*([^%s]+)%s*$"); file.remove(fn)  end
+, ["compile"] = function(ll,s) local fn = string.match(ll,"^%s*([^%s]+)%s*$");
+    local r,err = pcall(node.compile,fn); if not r then s("ERR: "..err) end
+  end
+, ["pread"] = function(ll,s) -- read b64 data from off in fn
+    local len, off, fn = string.match(ll,"^%s*(%d+)%s+(%d+)%s+([^%s]+)%s*$")
+    if fn == nil then s("ERR: Need file"); return end
+    withfat(s,"r",fn,off,function()
+        local out = file.read(tonumber(len))
+        if out == nil then s("ERR: Read err"); return end
+        s(encoder.toBase64(out))
+    end)
+  end
+, ["pwrite"] = function(ll,s) -- write b64 data at off in fn
+    local off, fn, edat = string.match(ll,"^%s*(%d+)%s+([^%s]+)%s*([^%s]+)%s*$")
+    if edat == nil then s("ERR: Malformed command"); return end
+    local ddat, err = encoder.fromBase64(edat)
+    if ddat == nil then s("ERR: "..err); return end
+    if tonumber(off) == 0 and not file.exists(fn) then file.open(fn,"w"); file.close() end
+    withfat(s,"r+",fn,off,function() if file.write(ddat) == nil then s("ERR: Write error") else s("OK") end end)
+  end
+, ["cert"] = function(ll,s) -- load argument as certificate root
+    local fn = string.match(ll,"^%s*([^%s]+)%s*$")
+    withfat(s,"r",fn,0,function()
+      local cert = ""
+      local chunk = file.read()
+      while chunk ~= nil do cert = cert..chunk; chunk = file.read() end
+      net.cert.verify(cert)
+    end)
+  end
+}
diff --git a/telnetd/telnetd.lua b/telnetd/telnetd.lua
new file mode 100644 (file)
index 0000000..ef6c5ff
--- /dev/null
@@ -0,0 +1,35 @@
+-- DEPEND: net ; fifosock
+local self = {}
+self.commands = { ["echo"] = function(r,s) s(r) end }
+function self.tryin(i,ft,nc,ns,...) -- input, function table, no-command, no-space, args
+  local ix = i:find("%f[%s]",1,false)
+  if (ix ~= nil) then
+    local c, r = i:sub(1,ix-1), i:sub(ix+1); local cf = ft[c]
+    if cf ~= nil then cf(r,...) else nc(c,r,...) end
+  else ns(i,...)
+  end
+end
+self.on = { ["conn"] = nil, ["disconn"] = nil }
+local function tryon(e,...) local c = self.on[e]; if c ~= nil then c(...) end end
+function self.rx(tx,input,k)
+  self.tryin(input, self.commands,
+    function(c,r)
+      if c == "quit" then k(false) else
+       local rt = loadfile(string.format("telnetd-%s.lc",c))
+       if rt ~= nil
+        then self.tryin(r,rt(),function(c2) tx(c.." "..c2.."?") end, function() tx(c.." ??") end,tx)
+        else tx(c.."?")
+    end end end,
+    function(_) tx("?") end,tx)
+  k(true)
+end
+function self.server(sock_)
+  local sock = (dofile("fifosock.lc")).wrap(sock_)
+  local dosend = function(...) sock:send(...) end
+  local k = function(c) if c then sock:send("\n$ ") else sock:close() end end
+  sock:on("receive",function(s_,input) self.rx(dosend,input,k) end)
+  sock:on("disconnection",function(s_) tryon("disconn",sock); sock.fini(); sock=nil end)
+  tryon("conn",sock)
+  sock:send("\n$ ")
+end
+return self
diff --git a/tq/tq-diag.lua b/tq/tq-diag.lua
new file mode 100644 (file)
index 0000000..129cced
--- /dev/null
@@ -0,0 +1,3 @@
+-- call directly or wrap in tq immediately to get updated leader time value:
+-- tq:queue(1,function() dofile("tq-diag.lc")(tq,print,print) end)
+return function (self,kt,ke) local i,t; for i,t in ipairs(self._q) do kt(i,t["t"],#t) for k,v in ipairs(t) do ke(i,k,v) end end end
diff --git a/tq/tq.lua b/tq/tq.lua
new file mode 100644 (file)
index 0000000..9fda3d8
--- /dev/null
+++ b/tq/tq.lua
@@ -0,0 +1,42 @@
+-- DEPENDS: tmr [only by default]
+local fire, doarm
+function fire(self)
+  local cbs = table.remove(self._q,1)
+  if #self._q > 0 then doarm(self, self._q[1]["t"]) end
+  local v
+  for _,v in ipairs(cbs) do v() end
+end
+function doarm(self,when) self:arm(function() fire(self) end, when); self._tst = self:now() end
+local function queue(self,when,what,...)
+  if #self._q > 0 then
+    local lapsed = (self:now() - self._tst)/1000
+    self._q[1]["t"] = self._q[1]["t"] - lapsed
+  end
+  local ix = 0; local tleft = when
+  while (ix < #self._q) do
+    if (tleft < self._q[ix+1]["t"]) then break end
+    ix = ix + 1; tleft = tleft - self._q[ix]["t"]
+  end
+  local warg = {...}; local nwarg = select('#',...)
+  local wfn = function () return what(unpack(warg,1,nwarg)) end
+  if ix == 0 then doarm(self,when)
+  elseif tleft == 0 then table.insert(self._q[ix],wfn); return
+  end
+  table.insert(self._q,ix+1,{["t"] = tleft, [1] = wfn})
+  if ix+1 < #self._q then
+    self._q[ix+2]["t"] = self._q[ix+2]["t"] - tleft
+  end
+  return wfn
+end
+local function dequeue(self,what)
+  local k,v,w
+  for _,v in pairs(self._q) do for k,w in ipairs(v) do
+    if w == what then table.remove(v,k) end
+  end end
+end
+local function defl_arm(self,fn,t) tmr.alarm(self.tmr, t, tmr.ALARM_SINGLE, fn) end
+local function defl_now(_) return tmr.now() end
+return function(tmrix) return {
+  _q = {}, _tst = 0,
+  ["arm"] = defl_arm, ["now"] = defl_now, ["tmr"] = tmrix, ["queue"] = queue, ["dequeue"] = dequeue
+} end
diff --git a/util/diag.lua b/util/diag.lua
new file mode 100644 (file)
index 0000000..83648d1
--- /dev/null
@@ -0,0 +1,12 @@
+-- DEPENDS: file, rtcfifo, node, wifi
+print('INFO:',string.format("major=%d minor=%d dev=%d chip=%d flash=%d fs=%d fm=%d fs=%d",node.info()))
+print('HEAP:', node.heap())
+print('WIFI:',wifi.getmode())
+print('MAC:',wifi.sta.getmac(), wifi.ap.getmac())
+print('HOST:',wifi.sta.gethostname())
+print('WSTA:',wifi.sta.getconfig())
+print('WAP:',wifi.ap.getconfig())
+print('IP:',wifi.sta.getip(), wifi.ap.getip())
+if rtcfifo.ready() ~= 0 then print('RTCF:',rtcfifo.count()) else print('RTCF:','NOT PREPARED') end
+print('FS:', file.fsinfo()); for k,v in pairs(file.list()) do print("",k,v) end
+print('GLOBAL:'); for k,v in pairs(_G) do print("",k,v) end
diff --git a/util/fifosock.lua b/util/fifosock.lua
new file mode 100644 (file)
index 0000000..0c0f882
--- /dev/null
@@ -0,0 +1,22 @@
+-- STATELESS
+local fs = {}
+function fs.wrap(sock)
+  local sf = {}
+  local sfe = true
+  local function sfd() if #sf > 0 then sock:send(table.remove(sf,1)) else sfe = true end end
+  sock:on("sent", sfd)
+  local nsock = {}
+  nsock.send = function(k,s)
+    if s == nil or s == "" then return end
+    table.insert(sf,s)
+    if sfe then sfe = false; sfd() end
+  end
+  nsock.fini = function() sf=nil; sock=nil end
+  local sockit = getmetatable(sock)["__index"]
+  setmetatable(nsock,{ __index = function(_,k)
+    local fn = sockit[k]
+    return function(a,...) if a == nsock then fn(sock,...) else fn(a,...) end end
+  end })
+  return nsock
+end
+return fs