-local resntpPeriod = 1800000
-local mqttHeartbeat = 600000
local mqttUser
-- some exported modules for overlay and REPL use
local mqttBcastPfx = string.format("lamp/%s/out",mqttUser)
local mqttHeartTopic = string.format("lamp/%s/boot",mqttUser)
cap = require "cap1188"
+remoteqtmrs = {}
+isTouch = false
+pendRemoteMsg = nil
local function drawfailsafe(t,fb,g,r,b) fb:fill(g,r,b) end
function loaddrawfn(name)
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
isDim = true
dimfactor = 0
gpio.write(3,gpio.LOW)
end
end
-function doremotedraw()
- if ledfb_claimed > 1
- then ledfb_claimed = 2
- else touchtmr:unregister(); ledfb = remotefb; remotetmr:start(); dodraw()
- end
+
+function removeremote()
+ local k,v
+
+ -- drop all pending script timers
+ for k,v in pairs(remoteqtmrs) do v:unregister() end
+ remoteqtmrs = {}
+
+ -- and the current remote animation's timer
+ remotetmr:unregister()
end
-function leddefault(fb,...) fb:fill(0,0,0); local ix; for ix = 25,32 do fb:set(ix,...) end end
+local function remotemsg(m)
+ if isTouch
+ then pendRemoteMsg = m
+ else
+ touchtmr:unregister()
+ ledfb = remotefb
+ removeremote()
+ dofile("lamp-remote.lc")(m)
+ end
+end
-- MQTT-driven local setting
-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
+nwfnet.onmqtt["lamp"] = function(c,t,m)
+ if t and m and t:find("^lamp/[^/]+/out") then remotemsg(m) end
+end
-- TODO: messages to specific lamps? Multiple brokers?
-function lamp_announce(fn,g,r,b) mqc:publish(mqttBcastPfx,string.format("new; draw %s %x %x %x;",fn,r,g,b),1,1) end
+function lamp_announce(fn,g,r,b)
+ mqc:publish(mqttBcastPfx,string.format("draw %s %x %x %x;",fn,r,g,b),1,1)
+end
-- mqtt setup
local mqtt_reconn_timer
if mqtt_beat_cron then mqtt_beat_cron:unschedule(); mqtt_beat_cron = nil end
if not mqtt_reconn_timer then mqtt_conn() end
remotetmr:unregister()
- loaddrawfn("xx")(remotetmr,remotefb,0,5,0); doremotedraw()
+ remotemsg("draw xx 4 0 0 ;")
elseif e == "mqttconn" and c == mqc then
if mqtt_reconn_timer then
mqtt_reconn_timer:unregister()
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()
+ remotemsg("draw xx 4 0 4 ;")
elseif e == "wstagoip" then
if not mqtt_reconn_poller then mqtt_reconn() end
- leddefault(remotefb,0,0,4); doremotedraw()
+ remotemsg("draw xx 0 0 4 ;")
elseif e == "wstaconn" then
- leddefault(remotefb,0,4,0); doremotedraw()
+ remotemsg("draw xx 0 4 0 ;")
end
end
-- initialize display
-loaddrawfn("xx")(remotetmr,remotefb,0,5,5); dodraw()
+remotemsg("draw xx 4 0 0;")
-- touch overlay loader
function ontouch_load() dofile("lamp-touch.lc") end
--- GLOBAL: tq, remotefb, doremotedraw, remotetmr, loaddrawfn, remotetqh, remotefifo
+-- GLOBAL: tq, remotefb, remotetmr, loaddrawfn, remoteqtmrs
-if not remotefifo then remotefifo = (require "fifo")() end
+local intloop
--- dequeue from remotefifo; if nothing to dequeue, clean up
-local function fdq()
- if not remotefifo:dequeue(function(k) k() end)
- then remotetqh = nil; remotefifo = nil
- end
-end
+-- dispatch table; functions take
+-- (arguments, label table)
+local vt = {
+ -- labels have already been collected, so just ignore them now
+ ['label'] = function() end,
--- queue to remotefifo; if fifo has emptied, fire callback immediately,
--- otherwise, let the functions manage their own timeline
-local function fq(what)
- remotefifo:queue(what,fdq)
-end
+ -- end means to break out of the processing loop
+ ['end'] = function() return true end,
-return function(msg)
-
- local vt = {
- ['new'] = function(_)
- -- really only sensible at the start of a message; throw out
- -- the existing fifo, if any, and stop whatever time event is
- -- pending
- remotefifo = (require "fifo")()
- tq:dequeue(remotetqh)
- end,
- ['wait'] = function(s)
- -- step the fifo in d milliseconds
- local d = tonumber(s)
- if d and d > 0 then fq(function() remotetqh = tq:queue(d,fdq) end) end
- end,
- ['draw'] = function(s)
- -- engage a drawing function and post a time event to pop the fifo
- -- on the next tick (for, e.g., delay's use). This is done on a
- -- callback to prevent deep stacks.
- local m,r,g,b = s:match("^(%w+)%s+(%x+)%s+(%x+)%s+(%x+)%s*$")
- if m then
- g = tonumber(g,16); r = tonumber(r,16); b = tonumber(b,16)
- fq(function()
- remotetmr:unregister()
- loaddrawfn(m)(remotetmr,remotefb,g,r,b); doremotedraw()
- remotetqh = tq:queue(1,fdq)
- end)
+ -- in means to queue up a timer pointing at a label (which must already be known)
+ ['in'] = function(s,ls)
+ local d,l = s:match("^(%d+)%s+(.*)")
+ if d and ls[l] then
+ s = ls[l]
+ d = (tonumber(d)) or 1000
+ local tn = #remoteqtmrs + 1
+ local t = tmr.create()
+ remoteqtmrs[tn] = t
+ t:alarm(d,tmr.ALARM_SINGLE,function()
+ remoteqtmrs[tn] = nil
+ intloop(s,ls)
+ end)
end
end,
- }
- vt["0"] = vt.draw -- XXX hack for backwards compatibility
+ -- draw is the real reason we're here
+ ['draw'] = function(s)
+ -- engage a drawing function and post a time event to pop the fifo
+ -- on the next tick (for, e.g., delay's use). This is done on a
+ -- callback to prevent deep stacks.
+ local m,r,g,b = s:match("^(%w+)%s+(%x+)%s+(%x+)%s+(%x+)%s*$")
+ if m then
+ g = (tonumber(g,16)) or 0; r = (tonumber(r,16)) or 0; b = (tonumber(b,16)) or 0
+ remotetmr:unregister()
+ loaddrawfn(m)(remotetmr,remotefb,g,r,b)
+ remotetmr:start()
+ dodraw()
+ end
+ end,
+}
- -- loop over all ;-delimited statements in the message and fire
- -- them off. Note that by default this will *append* to the fifo,
- -- making messages not entirely idempotent (unless they start with
- -- "new").
+-- loop over all ;-delimited statements in the message and fire them off.
+function intloop(msg,labels)
local c,as
for c,as in msg:gmatch("(%w*)%s*([^;]*);%s*") do
- if c and vt[c] then vt[c](as) end
+ if c and vt[c] then if vt[c](as,labels) then break end end
end
end
+
+return function(msg)
+ local labels = {}
+ local k,v,c,as,posn
+
+ -- drop any prior timers
+ removeremote()
+
+ -- loop over all ;-delimeted statements and collect labels,
+ -- which point at substrings of the message.
+ for c,as,posn in msg:gmatch("(%w*)%s*([^;]*);%s*()") do
+ if c == "label" then
+ local eposs = msg:find(";%s*end",posn)
+ if eposs ~= nil then
+ labels[as] = msg:sub(posn,eposs+1)
+ else
+ labels[as] = msg:sub(posn)
+ end
+ end
+ end
+
+ intloop(msg,labels)
+end
--- globals referenced: isblackout, dimfactor, isDim, dodraw, ledfb, ledfb_claimed, remotefb, remotetmr, lamp_announce, tq, loaddrawfn
+-- globals referenced: isblackout, dimfactor, isDim, dodraw, ledfb, remotefb, remotetmr, lamp_announce, tq, loaddrawfn
--
-- globals asserted: touchcolor, touchlastfn
--
for k,v in ipairs(touchfns) do if v == touchlastfn then touchfnix = k end end
local function claimfb()
- if ledfb_claimed == 0 then
- remotetmr:stop()
- ledfb_claimed = 1
- ledfb = touchfb
- end
+ removeremote()
+ isTouch = true
+ ledfb = touchfb
end
local set0 = function(o) return bit.bor(o,0x01) end
local function ontouchdone()
gpio.trig(6, "low", ontouch_load) -- unload overlay
+ isTouch = false
+
-- we did something. Announce it!
if ledfb == touchfb then
-- flash the four control LEDs to show the user that settings took
-- 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_claimed = 0; remotetmr:start(); doremotedraw()
- else ledfb_claimed = 0
+ if pendRemoteMsg ~= nil then
+ touchtmr:unregister()
+ ledfb = remotefb
+ dofile("lamp-remote.lc")(pendRemoteMsg)
+ pendRemoteMsg = nil
end
end
end
remotetmr = tmr.create()
+remoteqtmrs = {}
+
+function removeremote()
+ local k,v
+
+ -- drop all pending script timers
+ for k,v in pairs(remoteqtmrs) do v:unregister() end
+ remoteqtmrs = {}
+
+ -- and the current remote animation's timer
+ remotetmr:unregister()
+end
remotefb = {}