]> hydra-www.ietfng.org Git - acmetensortoys-esp-lua_lamp/commitdiff
Scripting language, of sorts.
authorNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Tue, 7 Nov 2017 22:37:29 +0000 (17:37 -0500)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Wed, 8 Nov 2017 07:21:19 +0000 (02:21 -0500)
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
lamp-remote.lua
lamp-touch.lua
linux-draw.lua

index 0e4f77b91264a89f20e389bec5a14dd5f2dcd612..046eba8bb134df796c980fdc7934d5344844be29 100644 (file)
--- 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
index ba38d33832a421d755606d76fa315578b9145381..c3f3da776c85b0ca736578939684ca6f67b711c1 100644 (file)
@@ -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
index 313faace6d7a575636bc285e100172548ce480e9..2564a5a2cd408eec53b9730be03e8d3ab73686dd 100644 (file)
@@ -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
 
index 13f6f469fb88986ee0bb5eb0e8d2bd25c62a3a3d..ca81eed1dbef6ab20bddf040246ab8d6414541af 100644 (file)
@@ -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 = {}