NBP/RTTY Telemetry Format v2

From Northeast Ohio Experimenters Club
Jump to navigation Jump to search

This is the specification for RTTY telemetry location data from noexc balloon projects.

RTTY Format

The RTTY is being sent over FM at 45 baud, space tone of 700Hz, mark tone of 870Hz with 1 start bit and 1.5 stop bits.

This format is default for most every amateur radio RTTY program out of the box.

Beacon Format

Our telemetry is an Arduino sending the location of the balloon in the following format:


The CRC-16 is sent in hexadecimal and includes everything after the training sequence field (NOT including the first colon), up to and including the colon before the CRC.

In other words, if the message is


the CRC would be run over


and the CRC would be "2EFF". This is the hexidecimal representation 0x2EFF. See below for more on the CRC-16.

The callsign may not appear in every message. This is to save transmit-time. In the event that the callsign doesn't appear, its field will simply be empty. Such a message might look like this (note the double colon):


and the CRC would likewise run over:


Latitude and longitude are both in decimal degrees and the altitude is in meters. Time is UTC time as reported by the GPS.


Clients should handle the fact that additional fields may be added (separated by :) at a later date, without breaking. If the client doesn't handle the field in any way (because it is too old to know about it), it should simply drop it.

As of this writing, there will never be a : sent within a field. However, if clients wish to future-proof, they can handle a : in a field by anticipating that it would be escaped with a \. For example, R1R1R1\n:KD8ZRC:[latitude]:[longitude]:[altitude]:[time]:hello\:there\n\n\n would have a last field that parses as "hello:there" as opposed to two fields that parse as "hello\" and "there".


There are quite a few different implementations of 16 bit CRCs. This section details our use of CRC-16 and how to produce the same CRC.

Our CRC uses a polynomial of 0x1021. We initialize the CRC value to 0xFFFF.

The code used in our telemetry system:

unsigned short crc16(char * crcmsg, unsigned int size)
  unsigned short poly = 0x1021;
  unsigned short crc = 0xffff;
  unsigned short byte = 0;

  while (size-- > 0) {
    byte = ((unsigned short)(*crcmsg++)) << 8;
    crc = crc ^ byte;

    int i;
    for(i = 0; i < 8; i++) {
      if (crc & 0x8000) {
        crc = (crc << 1) ^ poly;
      } else {
        crc = crc << 1;
  return crc;


Beacon Format

We have two aims with the formatting of the location protocol: parse-ability and human readability.

The leading Rs are a training sequence so that RTTY decoders can begin to decode our signal without loosing data that might otherwise be in the beginning of the message. The newline then begins the actual data on a new line, this was chosen from a human readability standpoint: its easier to have a location string start on a new line than be in the mix of other characters.

Data fields are then delimited by colons(":"). At the end there are three newline characters. We've included this as a indication to programs that might be parsing the text (including one written by one of our own) that the line is complete and ready to be parsed. Multiple newlines are added for redundancy (in case one isn't received).

RTTY Format

We want to get the most participation out of the ham community as possible, realizing that not everyone has a SSB receiver, we decided to go with RTTY over an FM channel (we optimized for participation over absolute SNR advantage).

We also went with 45 baud RTTY over a faster rate (300baud tested to work) because it provides some resilience on short fades over faster baud rates. Its also easier because most RTTY decoders default to this setting. Our GPS coordinates are transmitted about every 10 seconds, which we determine to be more than adequate for our needs.

Parsing Hints

When parsing, it is probably easiest to go line-by-line and simply ignore null lines ("") and lines which contain only RRRRR.

A simple parser might look something like this:

parseLine :: Parser RTTYLine
parseLine = do
  _ <- char ':'
  callsign' <- takeWhile1 (/= ':')
  _ <- char ':'
  longitude' <- takeWhile1 (/= ':')
  _ <- char ':'
  latitude' <- takeWhile1 (/= ':')
  _ <- char ':'
  altitude' <- takeWhile1 (/= ':')
  _ <- char ':'
  time' <- takeWhile1 (/= ':')

...which is a simplified version of what is in use in MapView.

On each newline (hGetLine in Haskell), see if the line is empty. If it is, discard it, otherwise send it to your parser.

You'll note that the parser above ignores the RRRRRR's. They are not relevant to us, and we can safely trash them and start immediately after that point (which will be a non-null, non-"RRRRR" line).