--- /dev/null
+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
+}
--- /dev/null
+-- 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
--- /dev/null
+#!/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"
--- /dev/null
+#!/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 {} }
--- /dev/null
+-- 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]()
--- /dev/null
+-- 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
--- /dev/null
+-- 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
--- /dev/null
+-- 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
--- /dev/null
+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
--- /dev/null
+-- 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
--- /dev/null
+-- 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
+}
--- /dev/null
+-- 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
+}
--- /dev/null
+-- 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
--- /dev/null
+-- 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
--- /dev/null
+-- 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
--- /dev/null
+-- 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
--- /dev/null
+-- 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