From cbeb49acb4b0341008094d7e5165623494a552da Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Sat, 15 Oct 2016 20:05:25 -0400 Subject: [PATCH] examples/lamp: refactor drawing, docs 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 | 114 ++++++++++++++++++++++++++++++++++++++++++-- draw-fill.lua | 1 + draw-heart.lua | 8 ++++ draw-laserchase.lua | 9 ++++ draw-snake.lua | 15 ++++++ draw-xx.lua | 8 ++++ init2.lua | 13 ++++- lamp-draw.lua | 46 ------------------ lamp-remote.lua | 15 +++--- lamp-touch.lua | 18 +++---- pushall.sh | 5 +- 11 files changed, 183 insertions(+), 69 deletions(-) create mode 100644 draw-fill.lua create mode 100644 draw-heart.lua create mode 100644 draw-laserchase.lua create mode 100644 draw-snake.lua create mode 100644 draw-xx.lua delete mode 100644 lamp-draw.lua diff --git a/README.rst b/README.rst index 0c3e2d8..de80ae5 100644 --- a/README.rst +++ b/README.rst @@ -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 index 0000000..27eee30 --- /dev/null +++ b/draw-fill.lua @@ -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 index 0000000..39dd90e --- /dev/null +++ b/draw-heart.lua @@ -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 index 0000000..51e2805 --- /dev/null +++ b/draw-laserchase.lua @@ -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 index 0000000..c4a191e --- /dev/null +++ b/draw-snake.lua @@ -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 index 0000000..87ff53f --- /dev/null +++ b/draw-xx.lua @@ -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 diff --git a/init2.lua b/init2.lua index 4d93e5e..a71dc7b 100644 --- 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 index 99e5caf..0000000 --- a/lamp-draw.lua +++ /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 -} diff --git a/lamp-remote.lua b/lamp-remote.lua index 048a739..14a0a91 100644 --- a/lamp-remote.lua +++ b/lamp-remote.lua @@ -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 diff --git a/lamp-touch.lua b/lamp-touch.lua index ef0bc47..cfa2719 100644 --- a/lamp-touch.lua +++ b/lamp-touch.lua @@ -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 diff --git a/pushall.sh b/pushall.sh index 84533da..b915c44 100755 --- a/pushall.sh +++ b/pushall.sh @@ -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" -- 2.50.1