]> hydra-www.ietfng.org Git - acmetensortoys-esp-lua_lamp/commitdiff
Initial checkin of framework and lamp example
authorNathaniel Wesley Filardo <nwfilardo@gmail.com>
Fri, 7 Oct 2016 03:27:43 +0000 (23:27 -0400)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Fri, 7 Oct 2016 17:20:32 +0000 (13:20 -0400)
init2.lua [new file with mode: 0644]
lamp-draw.lua [new file with mode: 0644]
lamp-remote.lua [new file with mode: 0644]
lamp-touch.lua [new file with mode: 0644]
pushall.sh [new file with mode: 0755]
telnetd-cap.lua [new file with mode: 0644]

diff --git a/init2.lua b/init2.lua
new file mode 100644 (file)
index 0000000..1471f0c
--- /dev/null
+++ b/init2.lua
@@ -0,0 +1,90 @@
+local resntpPeriod = 1800000
+local mqttHeartbeat = 600000
+local mqttUser
+
+-- some exported modules for overlay and REPL use
+-- XXX timer 5 reserved for the eventual day that we want animations in lamp-draw.lc
+tq = (dofile "tq.lc")(6)            -- timer 6
+nwfnet = require "nwfnet"
+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"
+
+-- telnetd overlay
+tcpserv = net.createServer(net.TCP, 120)
+tcpserv:listen(23,function(k)
+  local telnetd = dofile "telnetd.lc"
+  telnetd.on["conn"] = function(k) k:send(string.format("%s [NODE-%06X]",mqttUser,node.chipid())) end
+  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
+function dodraw() if not isblackout then ws2812.write(ledfb) end end
+function doremotedraw() if ledfb_claimed > 1 then ledfb_claimed = 2 else ledfb = remotefb; dodraw() end end
+
+function leddefault(fb,...) fb:fill(0,0,0); local ix; for ix = 25,32 do fb:set(ix,...) end end
+
+-- MQTT-driven local setting
+local 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
+
+-- TODO: messages to specific lamps?  Multiple brokers?
+function lamp_announce(fn,g,r,b) mqc:publish(mqttBcastPfx,string.format("0 %s %x %x %x",fn,r,g,b),1,1) end
+
+-- mqtt setup
+local mqtt_beat_cancel
+local mqtt_reconn_poller
+local function mqtt_reconn()
+  mqtt_reconn_poller = tq:queue(30000,mqtt_reconn)
+  mqc:close(); dofile("nwfmqtt.lc").connect(mqc,"nwfmqtt.conf")
+end
+
+-- network callbacks
+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(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
+    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()
+  elseif e == "wstagoip"              then
+    if not mqtt_reconn_poller then mqtt_reconn() end
+    leddefault(remotefb,0,0,4); doremotedraw()
+  elseif e == "wstaconn"              then
+    leddefault(remotefb,0,4,0); doremotedraw()
+  end
+end
+
+-- touch overlay loader
+function ontouch_load() dofile("lamp-touch.lc") end
+
+-- pin 6 (GPIO12) is cap sensor IRQ (active low)
+-- pin 5 (GPIO14) is cap sensor reset (active low)
+dofile("cap1188-init.lc").init(6,5,ontouch_load)
+
+-- initialize display
+dofile("lamp-draw.lc").xx(remotefb,0,5,5); dodraw()
+
+-- initialize network
+dofile("nwfnet-diag.lc")(true)
+dofile("nwfnet-go.lc")
diff --git a/lamp-draw.lua b/lamp-draw.lua
new file mode 100644 (file)
index 0000000..9cb22ea
--- /dev/null
@@ -0,0 +1,20 @@
+-- a table of functions that take framebuffers and colors
+return {
+  ["heart"] = function(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,
+  ["fill"] = function(fb,g,r,b) fb:fill(g,r,b) end,
+  ["xx"] = function(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/lamp-remote.lua b/lamp-remote.lua
new file mode 100644 (file)
index 0000000..97b1af2
--- /dev/null
@@ -0,0 +1,27 @@
+-- GLOBAL: tq, remotefb, leddefault, doremotedraw, mqtt_revert
+
+local function ledrevert(ix)
+  if ix < 3 then
+    remotefb:fade(2) doremotedraw()
+    tq:queue(500,function() ledrevert(ix+1) end)
+  else leddefault(remotefb,0,16,16) end
+  dodraw()
+end
+
+return function(m)
+  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*$")
+  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 type(f) == "table" and f[m]
+    if fn then fn(doremotedraw,remotefb,g,r,b)
+     else remotefb:fill(g,r,b); doremotedraw() -- failsafe
+    end
+    -- 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
+  end
+
+end
diff --git a/lamp-touch.lua b/lamp-touch.lua
new file mode 100644 (file)
index 0000000..a39c09b
--- /dev/null
@@ -0,0 +1,137 @@
+-- globals referenced: isblackout, dodraw, ledfb, ledfb_claimed, remotefb, lamp_announce, tq
+-- assumptions: gpio.trig(6) is the right thing to do for touch IRQs
+
+local touch_tq = (dofile "tq.lc")(5)
+local touchfb = ws2812.newBuffer(32,3)
+local touch_fini = nil
+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
+end
+
+local function claimfb()
+  if ledfb_claimed == 0 then
+    ledfb_claimed = 1
+    ledfb = touchfb
+  end
+end
+
+local set0 = function(o) return bit.bor(o,0x01) end
+local clear0 = function(o) return bit.band(o,0xFE) end
+local function setblackout(nb)
+  if nb then
+    isblackout = true
+    ws2812.write(string.char(0):rep(32*3))
+    cap:mr(0x81,function(o) return bit.bor(o,0x03) end) -- breathe
+    cap:mr(0x74,set0) -- drive
+    cap:mr(0x72,clear0) -- unlink
+  else
+    isblackout = false
+    dodraw()
+    cap:mr(0x81,function(o) return bit.band(o,0xFC) end) -- steady
+    cap:mr(0x74,clear0) -- undrive
+    cap:mr(0x72,set0) -- link
+  end
+end
+local function toggleblackout() setblackout(not isblackout) end
+
+local function touchcolorvec(c)
+  local r, g, b
+  local cm = c % 16
+  if     c < 16 then r = 15 - cm; g = cm; b = 0
+  elseif c < 32 then r = 0; g = 15 - cm; b = cm
+  else               r = cm; g = 0; b = 15 - cm
+  end
+  return g,r,b
+end
+
+local function onblackdebounce() touch_db_blackout = nil end
+local function onfndebounce() touch_db_fn = nil end
+
+local set30 = function(o) return bit.bor(o,0x1E) end
+local clear30 = function(o) return bit.band(o,0xE1) end
+local function ontouchdone()
+  gpio.trig(6, "low", ontouch_load) -- unload overlay
+
+  -- we did something.  Announce it!
+  if ledfb == touchfb then
+    -- flash the four control LEDs to show the user that settings took
+    cap:mr(0x74,set30) -- drive
+    cap:mr(0x72,clear30) -- unlink
+    tq:queue(100, function()
+      cap:mr(0x74,clear30) -- undrive
+      cap:mr(0x72,set30) -- link
+    end)
+
+    lamp_announce(touchfnsenc[touchfnix],touchcolorvec(touchcolor))
+  end
+
+  -- 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 = remotefb; dodraw() end
+  ledfb_claimed = 0
+end
+
+-- must not change ledfb to touchfb unless the user interacts with us
+local function ontouch()
+  local _, down = cap:rt()
+
+  if touch_fini ~= nil then touch_tq:dequeue(touch_fini) end
+
+  -- nothing down, kick off timer for touch done
+  if down == 0 then touch_fini = touch_tq:queue(1500,ontouchdone) end
+
+  -- back right button: display toggle once per touch of button
+  if bit.isset(down,0) then
+    if touch_db_blackout == nil then toggleblackout() else touch_tq:dequeue(touch_db_blackout) end
+    touch_db_blackout = touch_tq:queue(300,onblackdebounce)
+  end
+
+  if not isblackout then
+    -- front right buttons: local color wheel
+    if bit.isset(down,1) then
+      -- go forward quickly or slowly
+      if bit.isset(down,2) then touchcolor = touchcolor + 1 else touchcolor = touchcolor + 2 end
+      claimfb()
+    else
+      -- go backward, slowly
+      if bit.isset(down,2) then touchcolor = touchcolor - 1; claimfb() end
+    end
+    if     touchcolor >= 48 then touchcolor = touchcolor - 48
+    elseif touchcolor < 0  then touchcolor = touchcolor + 48
+    end
+
+    -- front middle: mode select (rate-limited, not exactly debounced)
+    if bit.isset(down,3) then
+      if touch_db_fn == nil then
+       touchfnix = touchfnix + 1
+       if touchfnix > #touchfns then touchfnix = 1 end
+       touch_db_fn = touch_tq:queue(200,onfndebounce)
+      end
+      claimfb()
+    end
+  end
+
+  -- XXX front left: no function assigned, maybe device select or something?
+  -- if bit.isset(down,4) then end
+
+  -- draw if we've claimed it!
+  if ledfb == touchfb then
+    touchfns[touchfnix](touchfb,touchcolorvec(touchcolor))
+    dodraw()
+  end
+end
+
+gpio.trig(6, "low", ontouch) -- hook overlay
+ontouch()
diff --git a/pushall.sh b/pushall.sh
new file mode 100755 (executable)
index 0000000..15b7740
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -e -u
+PUSHCMD="./host/pushvia.expect ${HOST} ${PORT:-23}"
+dopush() { ${PUSHCMD} ${2:-`basename $1`} $1; }
+dopushcompile() { ${PUSHCMD} ${2:-`basename $1`} $1 compile; }
+
+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
+dopush        examples/lamp/conf/nwfmqtt.conf
+dopush        examples/lamp/conf/nwfmqtt.subs
+dopushcompile examples/lamp/init2.lua
+
+echo "SUCCESS"
diff --git a/telnetd-cap.lua b/telnetd-cap.lua
new file mode 100644 (file)
index 0000000..4b508e7
--- /dev/null
@@ -0,0 +1,3 @@
+return {
+  ["calibrate"] = function(_,s) dofile("cap1188-init.lc").calibrate() end
+}