* any additional fields are to be ignored.
-* ``ctfws/game/flags`` -- whitespace-separated text field:
+* ``ctfws/game/flags`` -- the string ``?`` or a whitespace-separated text field:
* ``red`` -- red team flag capture count (int)
Example Command Line Usage
==========================
+For the sake of simplicity in the below examples, set::
+
+ M=(-h $MQTT_SERVER -u ctfwsmaster -P $CTFWSMASTER_PASSWD -q 2)
+
To watch what's going on in the world::
- mosquitto_sub -h $MQTT_SERVER -u ctfwsmaster -P asdf -q 1 -t ctfws/\# -v
+ mosquitto_sub "$M[@]" -t ctfws/\# -v
To send MQTT messages, try variants of these. Note that in all cases, we
set messages persistent so that devices that (re)connect mid-way into a game
* To start a game::
- mosquitto_pub -h $MQTT_SERVER -u ctfwsmaster -P asdf -q 1 -t ctfws/game/flags -r -m '0 0'
- mosquitto_pub -h $MQTT_SERVER -u ctfwsmaster -P asdf -q 1 -t ctfws/game/config -r -m `date +%s`' 900 3 900 10'
+ mosquitto_pub "$M[@]" -t ctfws/game/flags -r -m '0 0'
+ mosquitto_pub "$M[@]" -t ctfws/game/config -r -m `date +%s`' 900 3 900 10'
* To post information::
- mosquitto_pub -h $MQTT_SERVER -u ctfwsmaster -P asdf -q 1 -t ctfws/game/flags -r -m '1 2'
- mosquitto_pub -h $MQTT_SERVER -u ctfwsmaster -P asdf -q 1 -t ctfws/game/message -r -m 'Red team captured a flag!'
+ mosquitto_pub "$M[@]" -t ctfws/game/flags -r -m '1 2'
+ mosquitto_pub "$M[@]" -t ctfws/game/message -r -m 'Red team captured a flag!'
+
+* Note that you can deliberately hide the flag scores, if you like, by
+ publishing ``?`` to the ``/flags`` topic::
+
+ mosquitto_pub "$M[@]" -t ctfws/game/flags -r -m '?'
* To end a game::
- mosquitto_pub -h $MQTT_SERVER -u ctfwsmaster -P asdf -q 1 -t ctfws/game/endtime -r -m `date +%s`
+ mosquitto_pub "$M[@]" -t ctfws/game/endtime -r -m `date +%s`
Jail Glyph Timers
#################
One possible instantiation, just as a baseline:
+---+-------------------------------------------------------------+-------+
-| 1 | NodeMCU board (ESP8266+USB serial) | 3.00 |
+| 1 | NodeMCU board (ESP8266+USB serial) | 4.00 |
+---+-------------------------------------------------------------+-------+
-| 1 | 2.5Ah USB power stick | 6.00 |
+| 1 | 2.5Ah USB power stick | 5.50 |
+---+-------------------------------------------------------------+-------+
-| 1 | 4x20 LCD display | 7.00 |
+| 1 | 4x20 LCD display | 4.50 |
+---+-------------------------------------------------------------+-------+
-| 1 | Buzzer | 1.00 |
+| 1 | Buzzer | 0.20 |
+---+-------------------------------------------------------------+-------+
-| 1 | Small breadboard | 1.00 |
+| 1 | Small breadboard | 0.80 |
+---+-------------------------------------------------------------+-------+
-| | Jumper wire | 1.00 |
+| | Jumper wire | 0.50 |
+---+-------------------------------------------------------------+-------+
-| | TOTAL | 19.00 |
+| | TOTAL | 15.50 |
+---+-------------------------------------------------------------+-------+
Character Display
lcd:put(lcd:locate(1,0)," ")
end
if ctfws.startT then
- local str = string.format("%d\000: R=%d Y=%d",
- ctfws.flagsN, ctfws.flagsR, ctfws.flagsY)
+ 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)
end
end
local function drawMessage(self, msg)
local lcd = self.lcd
local mlen = (msg and #msg) or 0
- -- XXX chirp to get attention
self.mtmr:unregister()
lcd:put(lcd:locate(2,0)," ")
if not msg then return end
end
self.mtmr:alarm(300, tmr.ALARM_AUTO, scroller)
end
+ attention(self)
end
local function reset(self)
self.tq = tq
self.mtmr = t
- self.attnState = nil
+ self.attnState = nil
self.reset = reset
self.drawTimes = drawTimes
-- setupD -- deciseconds for setup round
-- roundD -- deciseconds per round
-- rounds* -- number of rounds of game play
--- startT -- NTP seconds of game start
+-- startT* -- NTP seconds of game start
-- endT -- NTP seconds of game end (if set)
--
-- flagsN* -- total flags
-- flagsR* -- flags captured by the red team
-- flagsY* -- flags captured by the yellow team
--
--- *'d fields are publicly read
+-- *'d fields are publicly read; startT ~= nil is used as a proxy for "is
+-- game configured"
-- returns round index, this round duration, elapsed time
-- round index: 0 for setup, 1-N for game play, and nil for game over / no game
mqc:lwt(mqttBootTopic,"dead",1,1)
-- This is not, properly speaking, OK, but it's so convenient
+local boot_message_hack = 1
+ctfws_lcd.attnState = 1 -- hackishly suppress attention() call
ctfws_lcd:drawMessage(string.format("I am: %s", mqttUser))
+ctfws_lcd.attnState = nil
local myBSSID = "00:00:00:00:00:00"
ctfws_lcd:reset()
ctfws_lcd:drawFlags()
ctfws_lcd:drawTimes()
+
+ -- clear the message display if it hasn't been already after boot
+ if boot_message_hack then
+ ctfws_lcd:drawMessage("")
+ boot_message_hack = nil
+ end
end
-local function ctfws_start_tmr()
- ctfws_tmr:alarm(100,tmr.ALARM_AUTO,function()
- if not ctfws_lcd:drawTimes() then ctfws_tmr:unregister() end
- end)
+local ctfws_start_tmr
+local function ctfws_tmr_cb()
+ -- draw the display, and if it tells us that the game is not in progress,
+ -- wait a little longer before trying again, but don't unregister (like we
+ -- used to). This means we'll paint error messages periodically, but
+ -- won't hammer the i2c bus with too many unnecessary updates. It also
+ -- means that a little NTP drift is OK.
+ if not ctfws_lcd:drawTimes() then
+ ctfws_tmr:alarm(3000,tmr.ALARM_AUTO,ctfws_start_tmr)
+ end
+end
+function ctfws_start_tmr()
+ ctfws_tmr:alarm(100,tmr.ALARM_AUTO,ctfws_tmr_cb)
end
nwfnet.onmqtt["init"] = function(c,t,m)
ctfws_lcd_draw_all()
ctfws_start_tmr() -- might have been unset; restart display if so
elseif t == "ctfws/game/flags" then
- if not m then ctfws:setFlags(0,0); return end
+ if not m or m == "" then
+ ctfws:setFlags("?","?")
+ ctfws_lcd:drawFlags()
+ return
+ end
local fr, fy = m:match("^%s*(%d+)%s+(%d+).*$")
if fr ~= nil then
ctfws:setFlags(tonumber(fr),tonumber(fy))
ctfws_lcd:drawFlags()
+ return
+ end
+ if m:match("^%s*%?.*$") then
+ ctfws:setFlags("?","?")
+ ctfws_lcd:drawFlags()
end
elseif t:match("^ctfws/game/message") then
+ boot_message_hack = nil
ctfws_lcd:drawMessage(m)
end
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",1)
- mqc:subscribe("ctfws/game/endtime",1)
- mqc:subscribe("ctfws/game/flags",1)
- mqc:subscribe("ctfws/game/message",1) -- broadcast messages
- mqc:subscribe("ctfws/game/message/jail",1) -- jail-specific messages
+ 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
ctfws_lcd:drawFlagsMessage("MQTT CONNECTED")
elseif e == "wstagoip" then
if not mqtt_reconn_cronentry then mqtt_reconn() end