]> hydra-www.ietfng.org Git - acmetensortoys-esp-lua_lamp/commitdiff
examples/lamp: refactor drawing, docs
authorNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Sun, 16 Oct 2016 00:05:25 +0000 (20:05 -0400)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Sun, 16 Oct 2016 00:05:25 +0000 (20:05 -0400)
Break drawings out to their own files, chase changes around the tree.

While here, dramatically improve documentation of the project.  That was
a low bar to pass. ;)

README.rst
draw-fill.lua [new file with mode: 0644]
draw-heart.lua [new file with mode: 0644]
draw-laserchase.lua [new file with mode: 0644]
draw-snake.lua [new file with mode: 0644]
draw-xx.lua [new file with mode: 0644]
init2.lua
lamp-draw.lua [deleted file]
lamp-remote.lua
lamp-touch.lua
pushall.sh

index 0c3e2d8c2d7be54590071f28c9ced8eb0c3c282d..de80ae5697f7e30cefbd648a3ac2ac113a65997b 100644 (file)
@@ -4,7 +4,7 @@ Remote Touch Lamp
 
 This somewhat sizable project is a re-implementation of
 ``http://filimin.com/`` of sorts.  It speaks MQTT and uses a CAP1188 for
-local touch sensing.
+local touch sensing.  Display is via a grid of so-called "NeoPixel" LEDs.
 
 Hardware V1
 ###########
@@ -15,8 +15,6 @@ Feather stack of components, as well as some others.  A complete BoM is:
 +------------------------+---------------------------------------------+
 | Case                   | https://www.adafruit.com/products/903       |
 +------------------------+---------------------------------------------+
-| Copper Tape            | https://www.adafruit.com/products/1128      |
-+------------------------+---------------------------------------------+
 | Feed Cable             | https://www.adafruit.com/products/744       |
 +------------------------+---------------------------------------------+
 | USB female             | https://www.adafruit.com/products/1833      |
@@ -31,5 +29,113 @@ Feather stack of components, as well as some others.  A complete BoM is:
 +------------------------+---------------------------------------------+
 | FeatherWing Doubler    | https://www.adafruit.com/products/2890      |
 +------------------------+---------------------------------------------+
-| CAP1188 BOB            | https://www.adafruit.com/products/1602      |
+| CAP1188 breakout board | https://www.adafruit.com/products/1602      |
++------------------------+---------------------------------------------+
+| Stacking headers       | https://www.adafruit.com/products/2830      |
 +------------------------+---------------------------------------------+
+| Female headers         | https://www.adafruit.com/products/2886      |
++------------------------+---------------------------------------------+
+| Copper Tape            | https://www.adafruit.com/products/1128      |
++------------------------+---------------------------------------------+
+| M/F jumper wires       | https://www.adafruit.com/products/826       |
++------------------------+---------------------------------------------+
+| F/F jumper wires       | https://www.adafruit.com/products/266       |
++------------------------+---------------------------------------------+
+
+The suggested configuration of the feather stack (hardly mandatory) is
+that the FeatherWing Doubler have two sets of female headers, the RGB array
+have male-only headers (downwards, for mating with the Doubler), and that
+the ESP8266 have stacking headers in place.  Hookup is then via the exposed
+female connectors on the top of the ESP8266 board.  One could, instead,
+stack the RGB array atop the ESP8266 and hookup via the Doubler's female
+headers, or do away with the Doubler entirely and hook the male pins on the
+ESP8266, etc.
+
+Theory of Operation
+###################
+
+Drawings
+########
+
+Drawings are kept in the filesystem and are only loaded on demand, in an
+effort to improve exensibility and keep heap usage low.
+
+``init2.lua`` publishes a global function ``loaddrawfn(name)`` which rummages
+through the filesystem to load ``draw-${name}.lc``.  These files are
+expected to *return a function* which takes, in order,
+
+* a timer, for use in animations.  It is guaranteed that this timer is
+  *unregistered* at the time of entry to the drawing functions, so there is
+  no need for static images to do so.  The timer will be started after the
+  drawing function returns and will be stopped when appropriate by the rest
+  of the system.
+
+* a ws2812 framebuffer object into which all drwaing should happen.  This
+  framebuffer should be closed over in any timer callbacks.
+
+* a color parameter, expressed as three arguments 0-255: green, red, blue
+  (as per ws2812 byte ordering).
+
+``lamp-touch.lua`` (see below) knows the naming scheme used by ``init2.lua``
+and so walks the list of files on the system looking for files whose name
+matches the Lua regex ``draw-(%w+).lc`` and builds a table of all such
+internally.
+
+Touch UI
+########
+
+``init2.lua`` hooks the CAP1188 IRQ pin and dispatches to ``lamp-touch.lua``
+to handle the user interface.  Once this file is loaded, it *replaces* the
+IRQ hook with its own function and unregisters itself (by replacing itself
+with ``init2.lua``'s ``ontouch_load`` function) after a timeout of no
+interaction from the user.  See ``ontouchdone``.
+
+At present, there are four used touch channels:
+
+* A "blackout" button, which disables drawing to the display and any other
+  user interaction until touched again.  This button may be toggled at most
+  once per touch interaction; once toggled, the user must allow the touch
+  handler to timeout.  This provides a crude "debounce" facility and allows
+  for things like moving the lamp to a new surface (with new conductive
+  properties) by gripping it in a way that touches this sensor and holding
+  it on the new surface for some seconds to allow the CAP1188 to
+  recalibrate.
+
+* Two color-wheel controls.  One advances "quickly", one reverses "slowly"
+  and, if both are active, the wheel advances "slowly".
+
+* A shape selector toggle.  This advances through the collections of
+  drawings enumerated at the beginning of a touch event.  Each separate
+  interaction will reload the list for ease of development.
+
+Notes
+#####
+
+The lamp has a primitive command interpreter listening on port 23.  Each
+command must fit entirely within one TCP packet, a complete and utter abuse
+of the protocol, but one that can usually be reasonably achieved.
+
+Useful commands include:
+
+* ``cap calibrate`` to force the CAP1188 to go through a calibration cycle.
+  While the module is configured to recalibrate itself periodically
+  automatically, one may wish to do so sooner especially if the sensors are
+  in a new environment that is just below the threshold of triggering
+  automatic recalibration.
+
+* ``diag exec LUA`` will ``pcall(loadstring(LUA))``, providing an emergency
+  escape hatch into the Lua interpreter without needing console access
+
+* ``file list`` will enumerate files on the flash
+
+* ``file info`` will show used and free space
+
+* ``file pwrite ...`` can write to the filesystem; don't use it by hand
+  unless you're *especially* masochistic.  Use ``host/pushvia.expect`` to
+  drive.
+
+* ``file compile FILE`` invokes ``node.compile(FILE)``
+
+* ``file remove FILE`` removes ``FILE``.
+
+* ``diag heap`` will display the number of free heap bytes
diff --git a/draw-fill.lua b/draw-fill.lua
new file mode 100644 (file)
index 0000000..27eee30
--- /dev/null
@@ -0,0 +1 @@
+return function(t,fb,g,r,b) fb:fill(g,r,b) end
diff --git a/draw-heart.lua b/draw-heart.lua
new file mode 100644 (file)
index 0000000..39dd90e
--- /dev/null
@@ -0,0 +1,8 @@
+return function(t,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
diff --git a/draw-laserchase.lua b/draw-laserchase.lua
new file mode 100644 (file)
index 0000000..51e2805
--- /dev/null
@@ -0,0 +1,9 @@
+return function(t,fb,g,r,b)
+  local ix = 1
+  local c = string.char(g,r,b)
+  fb:fill(0,0,0)
+  t:register(50,tmr.ALARM_AUTO,function()
+    fb:fade(2); fb:set(ix,c); dodraw()
+    ix = ix + 1; if ix > fb:size() then ix = 1 end
+  end)
+end
diff --git a/draw-snake.lua b/draw-snake.lua
new file mode 100644 (file)
index 0000000..c4a191e
--- /dev/null
@@ -0,0 +1,15 @@
+return function(t,fb,g,r,b)
+  local ix = 0
+  local c = string.char(g,r,b)
+  fb:fill(0,0,0)
+  fb:set(25,c) fb:set(26,c) fb:set(19,c) fb:set(28,c) fb:set(29,c) fb:set(30,c)
+  fb:set(23,c) fb:set(15,c) fb:set(7,c) fb:set(8,c)
+  t:register(1000,tmr.ALARM_AUTO,function()
+    if ix == 1
+     then fb:set(19,c) fb:set(27,0,0,0) fb:set(20,0,0,0) fb:set(28,c)
+     else fb:set(19,0,0,0) fb:set(27,c) fb:set(20,c) fb:set(28,0,0,0)
+    end
+    ix = 1 - ix
+    dodraw()
+  end)
+end
diff --git a/draw-xx.lua b/draw-xx.lua
new file mode 100644 (file)
index 0000000..87ff53f
--- /dev/null
@@ -0,0 +1,8 @@
+return function(t,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
index 4d93e5ed6e6f520a5460ee5b29a155245591b436..a71dc7bf3d12fe513cd2e5f6c51553a21f7d00ae 100644 (file)
--- a/init2.lua
+++ b/init2.lua
@@ -12,6 +12,14 @@ local mqttBcastPfx = string.format("lamp/%s/out",mqttUser)
 local mqttHeartTopic = string.format("lamp/%s/boot",mqttUser)
 cap = require "cap1188"
 
+local function drawfailsafe(t,fb,g,r,b) fb:fill(g,r,b) end
+function loaddrawfn(name)
+  local f = loadfile ("draw-%s.lc":format(name))
+  local fn = f and f()
+  if fn then return fn else return drawfailsafe end
+end
+
+
 -- telnetd overlay
 tcpserv = net.createServer(net.TCP, 120)
 tcpserv:listen(23,function(k)
@@ -65,7 +73,8 @@ 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(remotetmr,remotefb,0,5,0); doremotedraw()
+    remotetmr:unregister()
+    loaddrawfn("xx")(remotetmr,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
@@ -82,7 +91,7 @@ nwfnet.onnet["init"] = function(e,c)
 end
 
 -- initialize display
-dofile("lamp-draw.lc").xx(remotetmr,remotefb,0,5,5); dodraw()
+loaddrawfn("xx")(remotetmr,remotefb,0,5,5); dodraw()
 
 -- touch overlay loader
 function ontouch_load() dofile("lamp-touch.lc") end
diff --git a/lamp-draw.lua b/lamp-draw.lua
deleted file mode 100644 (file)
index 99e5caf..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
--- a table of functions that take framebuffers and colors
-return {
-  ["heart"] = function(t,fb,g,r,b)
-    t:unregister()
-    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(t,fb,g,r,b) t:unregister(); fb:fill(g,r,b) end,
-  ["xx"] = function(t,fb,g,r,b)
-    t:unregister()
-    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,
-  ["laserchase"] = function(t,fb,g,r,b)
-    local ix = 1
-    local c = string.char(g,r,b)
-    fb:fill(0,0,0)
-    t:register(50,tmr.ALARM_AUTO,function()
-      fb:fade(2); fb:set(ix,c); dodraw()
-      ix = ix + 1; if ix > fb:size() then ix = 1 end
-    end)
-  end,
-  ["snake"] = function(t,fb,g,r,b)
-    local ix = 0
-    local c = string.char(g,r,b)
-    fb:fill(0,0,0)
-    fb:set(25,c) fb:set(26,c) fb:set(19,c) fb:set(28,c) fb:set(29,c) fb:set(30,c)
-    fb:set(23,c) fb:set(15,c) fb:set(7,c) fb:set(8,c)
-    t:register(1000,tmr.ALARM_AUTO,function()
-      if ix == 1
-       then fb:set(19,c) fb:set(27,0,0,0) fb:set(20,0,0,0) fb:set(28,c)
-       else fb:set(19,0,0,0) fb:set(27,c) fb:set(20,c) fb:set(28,0,0,0)
-      end
-      ix = 1 - ix
-      dodraw()
-    end)
-  end
-}
index 048a7397d5a58b9dcf85c8318cfa82f3d23d824a..14a0a9160469f7d9ef899726cc0406193f4be4d5 100644 (file)
@@ -1,4 +1,4 @@
--- GLOBAL: tq, remotefb, leddefault, doremotedraw, mqtt_revert, remotetmr
+-- GLOBAL: tq, remotefb, leddefault, doremotedraw, mqtt_revert, remotetmr, loaddrawfn
 
 local function ledrevert(ix)
   if ix < 3 then
@@ -9,17 +9,16 @@ local function ledrevert(ix)
   dodraw()
 end
 
-return function(m)
+return function(msg)
   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*$")
+  local ix, _, d, m, r, g, b = msg: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 f()[m]
-    if fn then fn(remotetmr,remotefb,g,r,b); doremotedraw()
-     else remotefb:fill(g,r,b); doremotedraw() -- failsafe
-    end
+
+    remotetmr:unregister()
+    loaddrawfn(m)(remotetmr,remotefb,g,r,b); doremotedraw()
+
     -- 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
index ef0bc4704a6520371526e0a755986821dcd4ffb5..cfa2719ceac6fe444828f81e6feb77766a06a1e9 100644 (file)
@@ -1,4 +1,4 @@
--- globals referenced: isblackout, dodraw, ledfb, ledfb_claimed, remotefb, remotetmr, lamp_announce, tq
+-- globals referenced: isblackout, dodraw, ledfb, ledfb_claimed, remotefb, remotetmr, lamp_announce, tq, loaddrawfn
 -- assumptions: gpio.trig(6) is the right thing to do for touch IRQs
 
 local touch_tq = (dofile "tq.lc")(5)
@@ -8,15 +8,16 @@ 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
+for k,v in pairs(file.list()) do
+  local ix, _, meth = k:find("^draw-(%w+)\\.lc$")
+  if ix then
+    touchfns[#touchfns+1] = meth
+    if meth == "fill" then touchfnix = #touchfns end
+  end
 end
 
 local function claimfb()
@@ -74,7 +75,7 @@ local function ontouchdone()
       cap:mr(0x72,set30) -- link
     end)
 
-    lamp_announce(touchfnsenc[touchfnix],touchcolorvec(touchcolor))
+    lamp_announce(touchfns[touchfnix],touchcolorvec(touchcolor))
   end
 
   -- leave the ledfb pointing at us; it'll get updated eventually,
@@ -131,7 +132,8 @@ local function ontouch()
 
   -- draw if we've claimed it!
   if ledfb == touchfb then
-    touchfns[touchfnix](touchtmr,touchfb,touchcolorvec(touchcolor)); dodraw()
+    touchtmr:unregister()
+    loaddrawfn(touchfns[touchfnix])(touchtmr,touchfb,touchcolorvec(touchcolor)); dodraw()
     touchtmr:start()
   end
 end
index 84533da3ef4fe2f3ab338b1434af9c2ae7af4e82..b915c44dd7cefab4188973287508c91ed1a263c7 100755 (executable)
@@ -7,7 +7,6 @@ set -e -u
 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
@@ -15,4 +14,8 @@ dopush        examples/lamp/conf/nwfmqtt.conf
 dopush        examples/lamp/conf/nwfmqtt.subs
 dopushcompile examples/lamp/init2.lua
 
+for i in examples/lamp/draw-*.lua; do
+  dopushcompile ${i}
+done
+
 echo "SUCCESS"