Capture The Flag With Stuff Glyph Module
########################################
-
This is a hardware device designed to assist the `CMU KGB
<http://www.cmukgb.org/>`_ game of `Capture The Flag With Stuff
<http://www.cmukgb.org/activities/ctfws.php>`_.
| | TOTAL | 15.50 |
+---+-------------------------------------------------------------+-------+
+We have found it necessary, on occasion, to add a 100-ohm resistor between
+power and ground to keep the USB power sticks from automagically turning
+off due to low draw. It's not great, but it works.
+
NodeMCU Pinout
==============
0 1
01234567890123456789
- ROUND r/R : MM:SS.s
+ JB# n/N : MM:SS.s
NN⚑: R=NN Y=NN
messagemessagemessag
JAILBREAK : MM:SS.s
0 1
01234567890123456789
- ROUND r/R : MM:SS.s
+ GAME : MM:SS.s
NN⚑: R=NN Y=NN
messagemessagemessag
GAME END : MM:SS.s
0 1
01234567890123456789
- GAME OVER
+ CMUKGB CTFWS TIMER
NN⚑: R=NN Y=NN
messagemessagemessag
- GAME OVER
+ GAME OVER @ MM:SS
Game not configured::
0 1
01234567890123456789
- GAME NOT CONFIGURED
+ CMUKGB CTFWS TIMER
messagemessagemessag
GAME NOT CONFIGURED
end
end
+-- scroll a message msg across line lix using the timer t
+local function scroller(t, lix, msg)
+ local lcd = self.lcd
+ local mlen = #msg
+ if mlen <= 20
+ then lcd:put(lcd:locate(lix,(20-mlen)/2),msg)
+ else
+ -- inspired by lcd:run(), but corrected
+ local ix = 1
+ local function doscroll()
+ if ix <= 20 then lcd:put(lcd:locate(lix,20-ix),msg:sub(1,ix))
+ elseif ix > mlen then lcd:put(lcd:locate(lix,0),msg:sub(ix-19)," ")
+ else lcd:put(lcd:locate(lix,0),msg:sub(ix-19,ix))
+ end
+ if ix >= mlen + 20 then ix = 1 else ix = ix + 1 end
+ end
+ t:alarm(300, tmr.ALARM_AUTO, doscroll)
+ end
+end
+
local function drawNoGame(lcd, msg)
lcd:put(lcd:locate(0,0), " CMUKGB CTFWS TIMER ")
lcd:put(lcd:locate(3,0), " ")
local ctfws = self.ctfws
if self.dl_elapsed == nil then
lcd:put(lcd:locate(0,0), " ")
- if rix == 0 then
- lcd:put(lcd:locate(0,0), "SETUP :")
- else
- if ctfws.rounds >= 10
- then lcd:put(lcd:locate(0,0), string.format("JB# %2d/%2d :",rix,ctfws.rounds))
- else lcd:put(lcd:locate(0,0), string.format("JB# %d/%d :",rix,ctfws.rounds))
- end
+ if rix == 0 then lcd:put(lcd:locate(0,0), "SETUP :")
+ elseif rix == ctfws.rounds then lcd:put(lcd:locate(0,0), "GAME :")
+ elseif ctfws.rounds >= 11 then lcd:put(lcd:locate(0,0), string.format("JB# %2d/%2d :",rix,ctfws.rounds-1))
+ else lcd:put(lcd:locate(0,0), string.format("JB# %d/%d :",rix,ctfws.rounds-1))
end
end
drawDS(lcd,0,13,maxt,self.dl_elapsed,ela); self.dl_elapsed = ela
if self.dl_remain == nil then
lcd:put(lcd:locate(3,0), " ")
if rix == 0 then
- lcd:put(lcd:locate(3,0), "START IN :")
+ lcd:put(lcd:locate(3,0), "START IN :")
elseif rix < ctfws.rounds then
lcd:put(lcd:locate(3,0), "JAILBREAK :")
else
end
local function drawFlags(self)
- local lcd = self.lcd
- local ctfws = self.ctfws
if ctfws.flagsN then -- try not to blank a flagsmessage unless we have reason
+ self.ftmr:unregister()
lcd:put(lcd:locate(1,0)," ")
end
- if ctfws.startT then
- local str = string.format("%d\000: R=%s Y=%s",
- ctfws.flagsN, tostring(ctfws.flagsR), tostring(ctfws.flagsY))
- :sub(1,20)
- lcd:put(lcd:locate(1,(20-#str)/2), str)
- attention(self,false)
+ if ctfws.startT
+ then scroller(self.ftmr, 1, string.format("%d\000: R=%s Y=%s",
+ ctfws.flagsN, tostring(ctfws.flagsR), tostring(ctfws.flagsY)))
+ attention(self,false)
end
end
end
local function drawMessage(self, msg)
- local lcd = self.lcd
- local mlen = (msg and #msg) or 0
+ -- blank and stop scrolling
self.mtmr:unregister()
lcd:put(lcd:locate(2,0)," ")
+
if not msg then return end
- if mlen <= 20
- then lcd:put(lcd:locate(2,(20-#msg)/2),msg)
- else
- -- inspired by lcd:run(), but corrected
- local ix = 1
- local function scroller()
- if ix <= 20 then lcd:put(lcd:locate(2,20-ix),msg:sub(1,ix))
- elseif ix > mlen then lcd:put(lcd:locate(2,0),msg:sub(ix-19)," ")
- else lcd:put(lcd:locate(2,0),msg:sub(ix-19,ix))
- end
- if ix >= mlen + 20 then ix = 1 else ix = ix + 1 end
- end
- self.mtmr:alarm(300, tmr.ALARM_AUTO, scroller)
- end
+
+ scroller(self.mtmr, 2, msg)
attention(self,false)
end
self.dl_round = nil
end
-return function(ctfws, lcd, t)
+return function(ctfws, lcd, mt, ft)
self = {}
self.ctfws = ctfws
self.lcd = lcd
- self.mtmr = t
+ self.mtmr = mt
+ self.ftmr = ft
self.attnState = nil
-- setupD -- deciseconds for setup round
-- roundD -- deciseconds per round
-- rounds* -- number of rounds of game play
--- startT* -- NTP seconds of game start
--- endT -- NTP seconds of game end (if set)
+-- startT* -- POSIX seconds of game start
+-- endT -- POSIX seconds of game end (if set)
--
-- flagsN* -- total flags
-- flagsR* -- flags captured by the red team
return nil, "GAME NOT CONFIGURED!"
end
+ -- Game declared over; show total elapsed time
if self.endT and self.endT >= self.startT then
- return nil, "GAME OVER"
+ local t = self.endT - self.startT
+ return nil, string.format("GAME OVER @ %02d:%02d", t/60, t%60)
end
local now_sec, now_usec = nowf()
-- leave flagsN alone for end-of-game display logic
end
+-- return whether or not a change took place, for duplicate message
+-- suppression
local function setFlags(self, fr, fy)
+ if (self.flagsR == fr) and (self.flagsY == fy) then return false end
self.flagsR = fr
self.flagsY = fy
+ return true
end
local function setEndTime(self,t)
-- Hardware initialization
print("init2 hw")
+wifi.sta.sleeptype(wifi.NONE_SLEEP) -- don't power down radio
gpio.mode(5,gpio.OUTPUT) -- beeper on GPIO14
i2c.setup(0,2,1,i2c.SLOW) -- init i2c on GPIO4 and GPIO5
lcd = dofile("lcd1602.lc")(ctfwshw.lcd or 0x27)
+-- logging for debugging
+-- dprint = function(...) end -- OFF
+dprint = function(...) print(...) end -- ON
+
-- common module initialization
cron.schedule("*/5 * * * *", function(e) dofile("nwfnet-sntp.lc").dosntp(nil) end)
nwfnet = require "nwfnet"
ctfws:setFlags(0,0)
msg_tmr = tmr.create()
-ctfws_lcd = dofile("ctfws-lcd.lc")(ctfws, lcd, msg_tmr)
+flg_tmr = tmr.create()
+ctfws_lcd = dofile("ctfws-lcd.lc")(ctfws, lcd, msg_tmr, flg_tmr)
ctfws_tmr = tmr.create()
-- Draw the default display
local mqtt_reconn_cronentry
local function mqtt_reconn()
+ dprint("Trying reconn...")
mqtt_reconn_cronentry = cron.schedule("* * * * *", function(e)
mqc:close(); dofile("nwfmqtt.lc").connect(mqc,"nwfmqtt.conf")
end)
local mqtt_beat_cronentry
local function mqtt_beat()
- mqtt_beat_cronentry = cron.schedule("*/5 * * * *", function(e)
+ mqtt_beat_cronentry = cron.schedule("* * * * *", function(e)
mqc:publish(mqttBootTopic,string.format("beat %d %s",rtctime.get(),myBSSID),1,1)
end)
end
end
nwfnet.onmqtt["init"] = function(c,t,m)
+ dprint("MQTT", t, m)
if t == "ctfws/game/config" then
ctfws_tmr:unregister()
if not m or m == "none"
ctfws_start_tmr() -- might have been unset; restart display if so
elseif t == "ctfws/game/flags" then
if not m or m == "" then
- ctfws:setFlags("?","?")
- ctfws_lcd:drawFlags()
+ if ctfws:setFlags("?","?") then ctfws_lcd:drawFlags() end
return
end
local fr, fy = m:match("^%s*(%d+)%s+(%d+).*$")
if fr ~= nil then
- ctfws:setFlags(tonumber(fr),tonumber(fy))
- ctfws_lcd:drawFlags()
+ if ctfws:setFlags(tonumber(fr),tonumber(fy)) then ctfws_lcd:drawFlags() end
return
end
if m:match("^%s*%?.*$") then
- ctfws:setFlags("?","?")
- ctfws_lcd:drawFlags()
+ if ctfws:setFlags("?","?") then ctfws_lcd:drawFlags() end
end
elseif t:match("^ctfws/game/message") then
boot_message_hack = nil
-- network callbacks
nwfnet.onnet["init"] = function(e,c)
+ dprint("NET", e)
if e == "mqttdscn" and c == mqc then
if mqtt_beat_cronentry then mqtt_beat_cronentry:unschedule() mqtt_beat_cronentry = nil end
if not mqtt_reconn_cronentry then mqtt_reconn() end
if mqtt_reconn_cronentry then mqtt_reconn_cronentry:unschedule() mqtt_reconn_cronentry = nil end
if not mqtt_beat_cronentry then mqtt_beat() end
mqc:publish(mqttBootTopic,"alive",1,1)
- mqc:subscribe("ctfws/game/config",2)
- mqc:subscribe("ctfws/game/endtime",2)
- mqc:subscribe("ctfws/game/flags",2)
- mqc:subscribe("ctfws/game/message",2) -- broadcast messages
- mqc:subscribe("ctfws/game/message/jail",2) -- jail-specific messages
+ mqc:subscribe({
+ ["ctfws/game/config"] = 2,
+ ["ctfws/game/endtime"] = 2,
+ ["ctfws/game/flags"] = 2,
+ ["ctfws/game/message"] = 2, -- broadcast messages
+ ["ctfws/game/message/jail"] = 2, -- jail-specific messages
+ })
ctfws_lcd:drawFlagsMessage("MQTT CONNECTED")
elseif e == "wstagoip" then
if not mqtt_reconn_cronentry then mqtt_reconn() end