Show Source


The Carnegie Mellon University KGB plays a delightful game called Capture The Flag With Stuff. The game relies on wall-clock time and previously we just used a bunch of stop-watches manually set by hand. That stunk.

What have you done?

We have brought technology to bear. In particular, we use MQTT to dispatch messages about the current game state to subscribers and rely on NTP to keep devices in temporal synchrony. We have hardware devices, based around the ESP8266 and nodemcu, at the teams’ jails within the game, and additionally offer open, anonymous subscription to the data feed.

The devices’ firmware (in Lua) is available here. The device itself, at least for v1, looks like this:


Because machine-readable messages over MQTT is perhaps not the friendliest thing in the world, we have written several wrappers around the core, provided in addition to the devices themselves.

For player use, there is

  • an Android application (on the market) that I wrote (with some help from Cameron Wong), which can display various stats about the game.
  • a webpage (deployed here) which recreates much of the above application’s interface and has its own stylistic tweaks. Full credit to Michael Murphy for this.

Beside the webpage source, there are also some utility scripts for speaking the protocol, suitable for use by the head judge.

How Can I Help?

Would you like to write another frontend for us? Please feel free to observe the existing clients’ behaviors, but the below should be a more or less complete description of the wire protocol.

The Protocol

All numbers herein are base-10 encoded and devoid of leading zeros for ease of parsing.

MQTT messages should be set persistent so that devices that reboot or lose their connection will display the right thing upon reconnection. Most messages are designed to be idempotent, in the sense that they carry timestamps of their veracity, so out-of-order delivery is partially mitigated.

Topic Tree

Public, Centrally-set topics

The public aspects of the game are set under ctfws/game (e.g. ctfws/game/config). We grant read-only views of these topics to guest logins as well as our jail timers’ users.

  • config the string none or a whitespace-separated text field:

    • start_time – POSIX seconds indicating start state

    • setup_duration – setup duration, in seconds

    • rounds – number of rounds (intervals between jail breaks)

    • round_duration – seconds per round

    • nflags – number of flags per team

    • game_counter – (integer) which game in a bunch is this? Since often several are played in a night, it is likely useful to indicate to clients which game this is. The value 0 may be interpreted as suppressing indication in the client; we 1-index games to be friendly to people. ;)

    • territory_config – string; a site-specific descriptor of the territory configuration for this game.


      The CMU devices expect the contents of this field to be either dw (for the red team defending doherty) or wd.

    • any additional fields are to be ignored.

  • flags – a whitespace-separated text field. The first field is a POSIX timestamp; subsequent fields are either the string ? or:

    • red – red team flag capture count (int)
    • yel – yellow team flag capture count (int)
    • any additional fields are to be ignored.
  • endtime – a single number, denoting POSIX seconds of a forced game end. If this is larger than the last starttime gotten in a config message, then the game is considered over.

  • message – Message to be displayed everywhere. This and all other message/# topics have a POSIX-seconds timestamp followed by whitespace before the message body. These permit messages from previous games to be suppressed, should they end up resident on the MQTT broker.

  • message/player – Message to be displayed specifically to players.

  • message/jail – Message to be displayed specifically at jail glyph units.

  • message/reset – A single number, denoting POSIX seconds before which messages should not be displayed. This is useful in the event that the judges send out an incorrect message.

Private, Centrally-set topics

Some information is used internally by the judges; it is not intended for player view. These are set under the prefix ctfws/judge.

  • flags a whitespace-separated text string with two integer fields:

    • timestamp – the UNIX time of this message
    • red – An integer, encoding the set of red flags that have been captured. The 1 bit corresponds to flag A, 2 to flag B, etc. That is, if yellow has captured red flags C and H, this field has the value 132.
    • yellow – As above, but for the set of yellow flags that have been captured.


    This is just a proposal at the moment. There’s also the suggestion that flags be a whitespace-separated text string with two text fields, consisting of strings of letters for which flags have been captured, e.g., “CDH AB” if red has captured those three yellow flags and yellow those two red ones. Lord help us if we ever have more than 94 (the printable ASCII set, minus space) flags in play. The field need not be sorted.

Some information is communicated from the judges to devices directly. While there is no harm in users seeing this information, it is unlikely to be of interest:

  • ctfws/dev/$DEVICENAME/location Reserved for device-specific configuration, in particular for parsing ctfws/game/config‘s territory_config for display.
Device-set topics

Devices get to send messages to some topics, too, to provide centralized view of the world.

  • ctfws/dev/$DEVICENAME/beat

    • one of alive, beat, or dead (LWT; no further fields)
    • time (UNIX time, from local clock)
    • ap (MAC addr)
    • any additional fields are to be ignored.

    The device should publish alive at gain of MQTT connectivity and having registered a last will and testament to set the message dead. Thereafter, it should publish beat messages every minute.

ACL Configuration

For example:

# global read permissions
pattern read ctfws/game/#
pattern read ctfws/dev/+/beat

# allow devices to publish their heartbeats
pattern readwrite ctfws/dev/%u/#

# master write to all ctfws parameters
user ctfwsmaster
pattern readwrite ctfws/game/#
pattern readwrite ctfws/judge/#

Example Command Line Usage

For the sake of simplicity in the below examples, set:

M=(-h $MQTT_SERVER -u ctfwsmaster -P $CTFWSMASTER_PASSWD -q 1)

To watch what’s going on in the world:

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 get the latest messages automatically.

  • To start the 2nd game now, with 15 minutes of setup, 4 x 15 minute rounds, 10 flags, with red team defending Wean hall and the yellow team defending Doherty hall:

    mosquitto_pub “$M[@]” -t ctfws/game/flags -r -m ‘0 0’ mosquitto_pub “$M[@]” -t ctfws/game/config -r -m date +%s‘ 900 4 900 10 2’

  • To post information (The messages must have date stamps on the front!):

    mosquitto_pub "$M[@]" -t ctfws/game/flags -r -m '1 2'
    mosquitto_pub "$M[@]" -t ctfws/game/message -r -m `date +%s`' 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 "$M[@]" -t ctfws/game/endtime -r -m `date +%s`


Due to a bug in nodemcu (See, do not send messages with QoS 2; stick to QoS 1 and it appears to work. Ideally these should be QoS 2, but that will have to wait.