From aebf6886de7d9b3a45146c03ae28c9c463c2cca3 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Tue, 7 Nov 2017 17:37:29 -0500 Subject: [PATCH] Scripting language, of sorts. Messages now can use timers to switch between animations. For example, the single message label h; draw heartbeat f 0 0; in 3000 k; end; label k; draw kiss 0 f 0 ; in 3000 h; end; will spend three seconds on each animation, looping forever. The language may be subject to further refinement, of course. While here, rework handoff between the remote and touch components as well as the boot and network notification displays. --- init2.lua | 57 +++++++++++++++---------- lamp-remote.lua | 110 +++++++++++++++++++++++++++--------------------- lamp-touch.lua | 20 +++++---- linux-draw.lua | 12 ++++++ 4 files changed, 120 insertions(+), 79 deletions(-) diff --git a/init2.lua b/init2.lua index 0e4f77b..046eba8 100644 --- a/init2.lua +++ b/init2.lua @@ -1,5 +1,3 @@ -local resntpPeriod = 1800000 -local mqttHeartbeat = 600000 local mqttUser -- some exported modules for overlay and REPL use @@ -11,6 +9,9 @@ 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" +remoteqtmrs = {} +isTouch = false +pendRemoteMsg = nil local function drawfailsafe(t,fb,g,r,b) fb:fill(g,r,b) end function loaddrawfn(name) @@ -27,9 +28,6 @@ tcpserv:listen(23,function(k) 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) @@ -37,10 +35,6 @@ 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 @@ -61,21 +55,38 @@ function dodraw() 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 @@ -96,7 +107,7 @@ nwfnet.onnet["init"] = function(e,c) 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() @@ -106,17 +117,17 @@ nwfnet.onnet["init"] = function(e,c) 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 diff --git a/lamp-remote.lua b/lamp-remote.lua index ba38d33..c3f3da7 100644 --- a/lamp-remote.lua +++ b/lamp-remote.lua @@ -1,59 +1,75 @@ --- 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 diff --git a/lamp-touch.lua b/lamp-touch.lua index 313faac..2564a5a 100644 --- a/lamp-touch.lua +++ b/lamp-touch.lua @@ -1,4 +1,4 @@ --- 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 -- @@ -24,11 +24,9 @@ table.sort(touchfns) 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 @@ -83,6 +81,8 @@ local clear30 = function(o) return bit.band(o,0xE1) 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 @@ -99,9 +99,11 @@ local function ontouchdone() -- 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 diff --git a/linux-draw.lua b/linux-draw.lua index 13f6f46..ca81eed 100644 --- a/linux-draw.lua +++ b/linux-draw.lua @@ -76,6 +76,18 @@ function tmr.create() 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 = {} -- 2.50.1