--- /dev/null
+local resntpPeriod = 1800000
+local mqttHeartbeat = 600000
+local mqttUser
+
+-- some exported modules for overlay and REPL use
+-- XXX timer 5 reserved for the eventual day that we want animations in lamp-draw.lc
+tq = (dofile "tq.lc")(6) -- timer 6
+nwfnet = require "nwfnet"
+mqc, mqttUser = dofile("nwfmqtt.lc").mkclient("nwfmqtt.conf")
+local mqttBcastPfx = string.format("lamp/%s/out",mqttUser)
+local mqttHeartTopic = string.format("lamp/%s/boot",mqttUser)
+cap = require "cap1188"
+
+-- telnetd overlay
+tcpserv = net.createServer(net.TCP, 120)
+tcpserv:listen(23,function(k)
+ local telnetd = dofile "telnetd.lc"
+ telnetd.on["conn"] = function(k) k:send(string.format("%s [NODE-%06X]",mqttUser,node.chipid())) end
+ telnetd.server(k)
+end)
+
+-- Maybe SNTP sync periodically, if we ever come to care about the time (XXX?)
+-- dofile("nwfnet-sntp.lc").loopsntp(tq,resntpPeriod,nil)
+
+-- hardware setup
+ws2812.init(ws2812.MODE_SINGLE) -- uses GPIO2
+i2c.setup(0,2,1,i2c.SLOW) -- init i2c as per silk screen (GPIO4, GPIO5)
+
+-- and now we get to the lamp stuff
+remotefb = ws2812.newBuffer(32,3)
+ledfb = remotefb -- points at whichever buffer is appropriate to draw
+ledfb_claimed = 0 -- 0 : unclaimed, set remote immediately
+ -- 1 : claimed locally but remote has not changed
+ -- 2 : claimed locally but remote has changed
+
+isblackout = false
+function dodraw() if not isblackout then ws2812.write(ledfb) end end
+function doremotedraw() if ledfb_claimed > 1 then ledfb_claimed = 2 else ledfb = remotefb; dodraw() end end
+
+function leddefault(fb,...) fb:fill(0,0,0); local ix; for ix = 25,32 do fb:set(ix,...) end end
+
+-- MQTT-driven local setting
+local mqtt_revert = nil
+nwfnet.onmqtt["lamp"] = function(c,t,m) if t and m and t:find("^lamp/[^/]+/out") then dofile("lamp-remote.lc")(m) end end
+
+-- TODO: messages to specific lamps? Multiple brokers?
+function lamp_announce(fn,g,r,b) mqc:publish(mqttBcastPfx,string.format("0 %s %x %x %x",fn,r,g,b),1,1) end
+
+-- mqtt setup
+local mqtt_beat_cancel
+local mqtt_reconn_poller
+local function mqtt_reconn()
+ mqtt_reconn_poller = tq:queue(30000,mqtt_reconn)
+ mqc:close(); dofile("nwfmqtt.lc").connect(mqc,"nwfmqtt.conf")
+end
+
+-- network callbacks
+nwfnet.onnet["init"] = function(e,c)
+ if e == "mqttdscn" and c == mqc then
+ if mqtt_beat_cancel then mqtt_beat_cancel(); mqtt_beat_cancel = nil end
+ if not mqtt_reconn_poller then mqtt_reconn() end
+ dofile("lamp-draw.lc").xx(remotefb,0,5,0); doremotedraw()
+ elseif e == "mqttconn" and c == mqc then
+ if mqtt_reconn_poller then tq:dequeue(mqtt_reconn_poller); mqtt_reconn_poller = nil end
+ if not mqtt_beat_cancel then mqtt_beat_cancel = dofile("nwfmqtt.lc").heartbeat(mqc,mqttHeartTopic,tq,mqttHeartbeat) end
+ mqc:publish(mqttHeartTopic,"alive",1,1)
+ mqc:subscribe(string.format("lamp/+/out/%s",mqttUser),1)
+ dofile("nwfmqtt.lc").suball(mqc,"nwfmqtt.subs")
+ leddefault(remotefb,0,16,16); doremotedraw()
+ elseif e == "wstagoip" then
+ if not mqtt_reconn_poller then mqtt_reconn() end
+ leddefault(remotefb,0,0,4); doremotedraw()
+ elseif e == "wstaconn" then
+ leddefault(remotefb,0,4,0); doremotedraw()
+ end
+end
+
+-- touch overlay loader
+function ontouch_load() dofile("lamp-touch.lc") end
+
+-- pin 6 (GPIO12) is cap sensor IRQ (active low)
+-- pin 5 (GPIO14) is cap sensor reset (active low)
+dofile("cap1188-init.lc").init(6,5,ontouch_load)
+
+-- initialize display
+dofile("lamp-draw.lc").xx(remotefb,0,5,5); dodraw()
+
+-- initialize network
+dofile("nwfnet-diag.lc")(true)
+dofile("nwfnet-go.lc")
--- /dev/null
+-- a table of functions that take framebuffers and colors
+return {
+ ["heart"] = function(fb,g,r,b)
+ fb:fill(0,0,0)
+ local c = string.char(g,r,b)
+ fb:set( 3,c) fb:set( 5,c)
+ fb:set(10,c) fb:set(11,c) fb:set(12,c) fb:set(13,c) fb:set(14,c)
+ fb:set(19,c) fb:set(20,c) fb:set(21,c)
+ fb:set(28,c)
+ end,
+ ["fill"] = function(fb,g,r,b) fb:fill(g,r,b) end,
+ ["xx"] = function(fb,g,r,b)
+ fb:fill(0,0,0)
+ local c = string.char(g,r,b)
+ fb:set( 1,c) fb:set( 4,c) fb:set( 5,c) fb:set( 8,c)
+ fb:set(10,c) fb:set(11,c) fb:set(14,c) fb:set(15,c)
+ fb:set(18,c) fb:set(19,c) fb:set(22,c) fb:set(23,c)
+ fb:set(25,c) fb:set(28,c) fb:set(29,c) fb:set(32,c)
+ end
+}
--- /dev/null
+-- GLOBAL: tq, remotefb, leddefault, doremotedraw, mqtt_revert
+
+local function ledrevert(ix)
+ if ix < 3 then
+ remotefb:fade(2) doremotedraw()
+ tq:queue(500,function() ledrevert(ix+1) end)
+ else leddefault(remotefb,0,16,16) end
+ dodraw()
+end
+
+return function(m)
+ if mqtt_revert then tq:dequeue(mqtt_revert) end
+
+ local ix, _, d, m, r, g, b = m:find("^(%d+)%s+(%w+)%s+(%x+)%s+(%x+)%s+(%x+)%s*$")
+ if ix then
+ g = tonumber(g,16); r = tonumber(r,16); b = tonumber(b,16)
+ local f = loadfile "lamp-draw.lc"
+ local fn = f and type(f) == "table" and f[m]
+ if fn then fn(doremotedraw,remotefb,g,r,b)
+ else remotefb:fill(g,r,b); doremotedraw() -- failsafe
+ end
+ -- if there's a duration set, register a timer to reset the display to the default
+ local dn = tonumber(d)
+ if dn and dn > 0 then tq:queue(math.min(dn,6870947),ledrevert,0) end
+ end
+
+end
--- /dev/null
+-- globals referenced: isblackout, dodraw, ledfb, ledfb_claimed, remotefb, lamp_announce, tq
+-- assumptions: gpio.trig(6) is the right thing to do for touch IRQs
+
+local touch_tq = (dofile "tq.lc")(5)
+local touchfb = ws2812.newBuffer(32,3)
+local touch_fini = nil
+local touch_db_blackout = nil
+local touch_db_fn = nil
+local touchcolor = 40
+local touchfns = { }
+local touchfnsenc = { }
+local touchfnix = 1
+
+-- Whip through the drawing functions and build indexes
+local k,v
+for k,v in pairs(dofile("lamp-draw.lc")) do
+ touchfns [#touchfns+1 ] = v
+ touchfnsenc[#touchfnsenc+1] = k
+ if k == "fill" then touchfnix = #touchfns end
+end
+
+local function claimfb()
+ if ledfb_claimed == 0 then
+ ledfb_claimed = 1
+ ledfb = touchfb
+ end
+end
+
+local set0 = function(o) return bit.bor(o,0x01) end
+local clear0 = function(o) return bit.band(o,0xFE) end
+local function setblackout(nb)
+ if nb then
+ isblackout = true
+ ws2812.write(string.char(0):rep(32*3))
+ cap:mr(0x81,function(o) return bit.bor(o,0x03) end) -- breathe
+ cap:mr(0x74,set0) -- drive
+ cap:mr(0x72,clear0) -- unlink
+ else
+ isblackout = false
+ dodraw()
+ cap:mr(0x81,function(o) return bit.band(o,0xFC) end) -- steady
+ cap:mr(0x74,clear0) -- undrive
+ cap:mr(0x72,set0) -- link
+ end
+end
+local function toggleblackout() setblackout(not isblackout) end
+
+local function touchcolorvec(c)
+ local r, g, b
+ local cm = c % 16
+ if c < 16 then r = 15 - cm; g = cm; b = 0
+ elseif c < 32 then r = 0; g = 15 - cm; b = cm
+ else r = cm; g = 0; b = 15 - cm
+ end
+ return g,r,b
+end
+
+local function onblackdebounce() touch_db_blackout = nil end
+local function onfndebounce() touch_db_fn = nil end
+
+local set30 = function(o) return bit.bor(o,0x1E) end
+local clear30 = function(o) return bit.band(o,0xE1) end
+local function ontouchdone()
+ gpio.trig(6, "low", ontouch_load) -- unload overlay
+
+ -- we did something. Announce it!
+ if ledfb == touchfb then
+ -- flash the four control LEDs to show the user that settings took
+ cap:mr(0x74,set30) -- drive
+ cap:mr(0x72,clear30) -- unlink
+ tq:queue(100, function()
+ cap:mr(0x74,clear30) -- undrive
+ cap:mr(0x72,set30) -- link
+ end)
+
+ lamp_announce(touchfnsenc[touchfnix],touchcolorvec(touchcolor))
+ end
+
+ -- leave the ledfb pointing at us; it'll get updated eventually,
+ -- unless there was a remote message while we were doing our thing
+ -- in which case, display it now
+ if ledfb_claimed == 2 then ledfb = remotefb; dodraw() end
+ ledfb_claimed = 0
+end
+
+-- must not change ledfb to touchfb unless the user interacts with us
+local function ontouch()
+ local _, down = cap:rt()
+
+ if touch_fini ~= nil then touch_tq:dequeue(touch_fini) end
+
+ -- nothing down, kick off timer for touch done
+ if down == 0 then touch_fini = touch_tq:queue(1500,ontouchdone) end
+
+ -- back right button: display toggle once per touch of button
+ if bit.isset(down,0) then
+ if touch_db_blackout == nil then toggleblackout() else touch_tq:dequeue(touch_db_blackout) end
+ touch_db_blackout = touch_tq:queue(300,onblackdebounce)
+ end
+
+ if not isblackout then
+ -- front right buttons: local color wheel
+ if bit.isset(down,1) then
+ -- go forward quickly or slowly
+ if bit.isset(down,2) then touchcolor = touchcolor + 1 else touchcolor = touchcolor + 2 end
+ claimfb()
+ else
+ -- go backward, slowly
+ if bit.isset(down,2) then touchcolor = touchcolor - 1; claimfb() end
+ end
+ if touchcolor >= 48 then touchcolor = touchcolor - 48
+ elseif touchcolor < 0 then touchcolor = touchcolor + 48
+ end
+
+ -- front middle: mode select (rate-limited, not exactly debounced)
+ if bit.isset(down,3) then
+ if touch_db_fn == nil then
+ touchfnix = touchfnix + 1
+ if touchfnix > #touchfns then touchfnix = 1 end
+ touch_db_fn = touch_tq:queue(200,onfndebounce)
+ end
+ claimfb()
+ end
+ end
+
+ -- XXX front left: no function assigned, maybe device select or something?
+ -- if bit.isset(down,4) then end
+
+ -- draw if we've claimed it!
+ if ledfb == touchfb then
+ touchfns[touchfnix](touchfb,touchcolorvec(touchcolor))
+ dodraw()
+ end
+end
+
+gpio.trig(6, "low", ontouch) -- hook overlay
+ontouch()
--- /dev/null
+#!/bin/bash
+
+set -e -u
+PUSHCMD="./host/pushvia.expect ${HOST} ${PORT:-23}"
+dopush() { ${PUSHCMD} ${2:-`basename $1`} $1; }
+dopushcompile() { ${PUSHCMD} ${2:-`basename $1`} $1 compile; }
+
+dopushcompile net/nwfmqtt.lua
+dopushcompile cap1188/cap1188.lua
+dopushcompile cap1188/cap1188-init.lua
+dopushcompile examples/lamp/lamp-draw.lua
+dopushcompile examples/lamp/lamp-touch.lua
+dopushcompile examples/lamp/lamp-remote.lua
+dopushcompile examples/lamp/telnetd-cap.lua
+dopush examples/lamp/conf/nwfmqtt.conf
+dopush examples/lamp/conf/nwfmqtt.subs
+dopushcompile examples/lamp/init2.lua
+
+echo "SUCCESS"
--- /dev/null
+return {
+ ["calibrate"] = function(_,s) dofile("cap1188-init.lc").calibrate() end
+}