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:

A picture of the CtFwS jail timer device

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. Additionally, some daemons run on our webserver to provide autonomic features are available at


This project lives in the ee part of my website mostly as a historical accident; I put it here because it involved hardware, but there’s very little engineering to be found, and it is now almost entirely discussion of software.

How Can I Help?

All the usual open-source ways :Bug hunting, documentation…

Would you like to write or build 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. Unless otherwise indicated, numbers are non-negative.

$DEVICENAME refers to the MQTT user identity given to the device in question.

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 series of fields:

    • 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. We reserve - to mean that this field is being left unspecified despite later values (if any) being specified.


      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.

  • 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.

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

    • red – red team flag capture count (int, negatives OK)
    • yel – yellow team flag capture count (int, negatives OK)
    • any additional fields are to be ignored.
  • 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’ interfaces (i.e., web, Android).

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

  • message/jail/# – Reserved for messages directed to a particular jail glyph; at present, our devices do not subscribe to these endpoints.

  • messagereset – 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.

There are some additional publicly-set topics not under ctfws/game as they do not pertain to a particular game, but rather to the world more generally:

  • ctfws/timesync – a single number, denoting POSIX seconds at the time of its publication. The head judge’s computer or the broker should publish to this topic periodically (every minute?) to assist clients in measuring their clock skew. Clients must ignore retained messages on this topic, as they are by definition stale; messages should be published with QoS 0: a delayed message is worse than no message.
Rule Documentation URLs

Under ctfws/rules, some URLs to user-visible documentation are published. Messages for here are composed of whitespace-separated fields:

  • url – a URL whence the document may be downloaded;
    (spaces are to be URL-encoded, naturally enough).
  • time – (integer) POSIX seconds at which the handbook was last modified
  • sha256 – A hex encoding of the SHA256 of the handbook file.

The time and sha256 fields are to assist clients in suppressing fetches when initially subscribing or when the publication daemons spuriously announce the old result (which they might, for example, on startup).

The following topics are defined:

  • ctfws/rules/handbook/html – a single-HTML-page version of the handbook.
  • ctfws/rules/cheatsheet/pdf – a PDF version of the “cheatsheet”, a half-page or so summary of useful material.

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 players seeing this information, it is unlikely to be of interest:

  • ctfws/devc/$DEVICENAME/location Reserved for device-specific configuration, in particular for parsing ctfws/game/config’s territory_config for display.


    The CMU devices are likely to use either the character d or w to indicate the location of the device.

  • ctfws/devc/$DEVICENAME/role Reserved for device-specific configuration. Jail timers should either not have this set or should use the reserved value j; other devices may be assigned other roles, should we ever branch out.

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
    • 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.

    All fields other than the first are optional, with - being reserved for the case of optional fields being elided but later fields being specified.

ACL Configuration

For example:

# global read permissions
pattern read      ctfws/game/#
pattern read      ctfws/dev/#
topic   read      ctfws/timesync
topic   read      ctfws/rules/#

# the broker metadata can also be public
pattern read      $SYS/#

# allow devices to read configs and publish their topics
pattern read      ctfws/devc/%u/#
pattern readwrite ctfws/dev/%u/#

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

# host to write timesync values
user ctfwstimesyncd
topic readwrite   ctfws/timesync

# host to write rules values
user ctfwsrulesd
topic readwrite   ctfws/rules/#

Example Command Line Usage

These are kept for developer reference; the web interface offered by (and in particular is a vastly superior experience.

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 `date +%s`'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 `date +%s`'?'
  • 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.