From: Nathaniel Wesley Filardo Date: Fri, 7 Oct 2016 03:27:43 +0000 (-0400) Subject: Initial checkin of framework and lamp example X-Git-Url: https://hydra-www.ietfng.org/gitweb/?a=commitdiff_plain;h=61694203b22a859191300e46241e9120e5a103bf;p=acmetensortoys-esp-lua_lamp Initial checkin of framework and lamp example --- 61694203b22a859191300e46241e9120e5a103bf diff --git a/init2.lua b/init2.lua new file mode 100644 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 index 0000000..9cb22ea --- /dev/null +++ b/lamp-draw.lua @@ -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 index 0000000..97b1af2 --- /dev/null +++ b/lamp-remote.lua @@ -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 index 0000000..a39c09b --- /dev/null +++ b/lamp-touch.lua @@ -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 index 0000000..15b7740 --- /dev/null +++ b/pushall.sh @@ -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 index 0000000..4b508e7 --- /dev/null +++ b/telnetd-cap.lua @@ -0,0 +1,3 @@ +return { + ["calibrate"] = function(_,s) dofile("cap1188-init.lc").calibrate() end +}