GOES-13 is a geostationary weather satellite launched on the 24th of May 2006 under operation of NOAA (although technically under control of the US DoD and called EWS-G1), in mid 2020 it started drifting towards Europe in its storage orbit, in response to this the US DoD decided to pull it out of its graveyard orbit to replace Meteosat-8 for getting monitoring the weather over Europe.

It transmits multiple downlinks within L-band, the observed downlinks so far include: telemetry (1692.7 MHz), PDR/GVAR (1685.7 MHz) and CDA/SD (1676 MHz, potentially terminated as of writing). PDR is intended to effectively be LRIT++.

Fortunately, PDR does not use any encryption or obscurification, due to the fact that GOES-13 is only capable of L and S band downlinks it is impossible for the DoD to turn off both PDR and SD, so some form of L band downlink will remain as long as the satellite is in service. The protocol itself is a remnant of a much older protocol called AAA, which used back in the 1980s. The protocol used on GOES-13, GVAR, is slightly different.

For decoding an official decoder from NASA called gvartool is available, it is intended to work under perfect signal conditions, so a new decoder was required that was capable of dealing with a signal containing a large amount of bit flips.

The process for decoding started with demodulation of an IQ baseband provided by @ZSztanga and then later a full disk from @HRPTEgor. The demodulator was made in GNURadio and is that of a standard BPSK demodulator for 2.11136 MSPS.

The actual data within the downlink is pseudo randomized and differentially encoded with NRZ-S. This satellite doesn’t actually append a sync marker to the data per se, rather it’s pseudo randomly encodes 0s and then switches to data after 10032 bits, this is represented by DATA ENABLE and AND GATE in the diagram below taken from the GVAR specification.

GVAR Block Synchronization Preamble Encoding

The first step is NRZ-S decoding, the implementation in GVAR-Tools is suboptimal but works, it was modified from libsathelper’s NRZ-M with minor modification. After differential decoding we are at a stage where we can sync the data against a sync marker.

Generating the sync marker is a rather trivial task due to the basic randomization used within GVAR, take for example a simplified snippet from syncgen.cpp, where getBit referrers to a function similar in function to bool getBit(T data, int bit)

uint16_t shifter = 051665; // "The shift register is preset to 51665 octal"

for (int i = 0; i < 10032; i++){
    bool bit = getBit(shifter, 14) ^ getBit(shifter, 7);
    shifter = shifter << 1 | bit;
    std::cout << bit;

GVAR-Tools only uses the last 64 bits (allowing up to 3 incorrect bits) of the sync marker for practically reasons, the deframer required is very generic (in this case a modified version of Aang23’s SimpleDeframer), with the only somewhat special feature being the ability to handle variable length frames, due to the differential encoding there is no need to worry about inversion.

After we have aligned frames we have to derandomize the data, with the random register just continuing on after the sync marker the only difference to the above snippet is that it is written into a derandomization table, which is then XOR’ed with every bit of each frame.

Once we have gotten to this part we have full decoded frames containing usable data, the only step required now is to filter them by block ID and deinterleave certain channels. The first byte after the sync marker is the block ID, which is effectively a VCID, each block contains data from one of the detectors. Blocks are as follows

  • Block 0 - The documentation block, contains telemetry
  • Block 1 - Channels 1 and 2, Channel 1 being interleaved as odd/even, and Channel 2 having every other row skipped
  • Block 2 - Channels 3 and 4, Channel 3 being interleaved as odd/even, and Channel 4 having every other row skipped
  • Blocks 3-10 - Channel 5, the high resolution visible channel scans 8 lines at once with 8 separate visible detectors.

All channels are in 10 bit depth and in a 1.75:1 X:Y ratio, channels 1-4 are 4km/px (5000px wide) and channel 5 is 1km/px (20000px wide). To be space efficient the 10 bit value of each pixel is encoded as 10 bit which means some binary magic is needed, take for example this snippet from Aang23’s GVAR-Ingestor:

image[i    ] =  (buffer[0] << 2      ) | (buffer[1] >> 6);
image[i + 1] = ((buffer[1] % 64) << 4) | (buffer[2] >> 4);
image[i + 2] = ((buffer[2] % 16) << 6) | (buffer[3] >> 2);
image[i + 3] = ((buffer[3] % 4 ) << 8) |  buffer[4];

To sum up, the process for decoding GOES-13’s GVAR is as follows:

  1. Demodulation
  2. NRZ-S differential decoding
  3. Framing
  4. Pseudo derandomization
  5. Parse header and filter by Block ID
  6. Put data into an image

After all of this you can finally enjoy an image from GOES-13, or you could just use GVAR-Tools or GVAR-Ingestor ^_^

Image from GOES-13 on 21/11/2020