Decoding packets from GOMX-3: CSP packets

A few days ago, I talked about the modulation and coding used by the satellite GOMX-3. Here, I will look at the contents of the CSP packets transmitted by this satellite.

Mike DK3WN has kindly sent me a document from GomSpace which describes the beacon format. Unfortunately, it seems that this document is not accurate enough to implement a good telemetry decoder. The team has probably changed a bit the beacon fields later in the development process. The relevant information in this document is as follows. The telemetry beacons are sent by the OBC (CSP address 1) and the ADCS (CSP address 4). The OBC can transmit beacons of the types 0, 1 and 2. The ADCS can transmit beacons of the types 0, 1, 2, 3, 4, 5 and 6. To identify the format a beacon packet, we need to know the unit that sent it (the CSP source address of the packet) and the type (which is the first byte of the CSP data). For each of the units and types, there is a C struct that specifies how the rest of the data is organized. However, the C structs in this document are not totally accurate. The lengths of the packets do not match, so they’ve probably added some fields and perhaps moved a few things around. The document also says that beacons are always transmitted to CSP address 10, port 30. This seems to be true.

However, there are still some things that we can decode. First, we can look at the CSP headers of the packets. To do so, we can use gr-csp, which is a set of blocks to handle CSP packets in GNUradio. We can classify the decoded packets according to CSP addresses and ports, and packet length. Since we also know that the first byte of the data indicates beacon type, we can also include this into our classification. In the recording I’m using, I get the following packets:

  • CSP source 1 port 0. CSP destination 10 port 30. Length 144 bytes. Beacon type 0 (6 packets of this type)
  • CSP source 4 port 0. CSP destination 10 port 30. Length 170 bytes. Beacon type 0 (5 packets of this type)
  • CSP source 4 port 0. CSP destination 10 port 30. Length 153 bytes. Beacon type 1 (5 packets of this type)
  • CSP source 4 port 0. CSP destination 10 port 30. Length 205 bytes. Beacon type 2 (5 packets of this type)
  • CSP source 4 port 0. CSP destination 10 port 30. Length 198 bytes. Beacon type 3 (5 packets of this type)
  • CSP source 5 port 1. CSP destination 10 port 60. Length 28 bytes. (3 packets of this type)

Another thing we can look at is the CSP flags. Only the CRC flag is set. This means that the packets carry a CRC checksum. I’ve found that it is very useful to look at the code of libcsp to find out how several things work in the CSP protocol. The implementation of CRC is in csp_crc32.c. There, we see that the last 4 bytes of the packet store the CRC-32 of the packet in big-endian format. It is optional to include the 4 byte CSP header in the computation of the CRC.

This had me confused for a while. I was trying to compute the CRC-32 of the packets independently to see if it matched the last 4 bytes in the packet, but it didn’t match. The problem is that the CRC used in CSP is not the more usual CRC-32 (associated polynomial 0x04C11DB7), but rather CRC-32C (associated polynomial 0x1EDC6F41). There is no mention of this anywhere in the code, but I finally discovered that other implementations of CRC-32C use the same lookup table as libcsp does. So that’s something important to keep in mind: in CSP with CRC, the last 4 bytes of the packet store the CRC-32C of the packet in big-endian format. In the case of GOMX-3, the CSP header is not include in the CRC computation.

Before looking at the beacon packets, let’s look at the tiny 28 byte packets. I put one of those in the previous post. They look like:

pdu_length = 28
contents = 
0000: 01 01 af 8a 00 01 02 03 04 05 06 07 08 09 0a 0b 
0010: 0c 0d 0e 0f 10 11 12 13 cc 79 eb e6 
CSP header:
        Priority:               2
        Source:                 5
        Destination:            10
        Destination port:       60
        Source port:            1
        Reserved field:         0
        HMAC:                   0
        XTEA:                   0
        RDP:                    0
        CRC:                    1

This packet is a CSP ping reply from GOMX-3 to its ground station. The clue to notice this is to look at the reserved ports in csp_types.h. We see that port 1 corresponds to CSP_PING. By the way, in this file we also see that priority 2 means normal priority.

The implementation of CSP ping is in csp_services.c. The function csp_ping() there sends a CSP ping request. We see that the request is sent from some free port to port 1. The contents of the ping packet are just the bytes 0x00, 0x01, etc. The ping reply is sent from port 1 and the contents of the packet are just echoed back. Therefore, we see that our tiny packet is a 20 byte CSP ping reply. The CSP source address is 5, which according to GomSpaces documents corresponds to the COM unit.

I haven’t been able to find in libcsp the code that makes CSP ping replies. It seems that it should be in csp_service_handler.c, but the code there only logs that a request has being received.

Now let’s look at an OBC beacon of type 0.

0000: 01 80 a7 82 00 57 2f 0e 5c 2d 5f 2d 6c 1e 1d 3b
0010: 1c 00 03 00 44 00 00 00 44 00 69 00 08 00 6d 00
0020: 6d 01 96 00 be 01 c1 00 8e ff fb ff fd ff fa ff
0030: f9 ff f9 ff f9 02 57 2f 0e 5c ff c2 ff d1 ff a0
0040: fc c6 ff 96 57 2f 0e 5c 00 00 00 04 00 00 ff b7
0050: ff b7 57 2f 0e 5c 00 06 00 05 00 05 00 01 00 0d
0060: 00 b6 ff b3 ff b6 57 2f 02 54 00 c5 00 2e 00 10
0070: 00 0a 00 15 00 1f 00 15 00 7c 6b 11 c2 14 ab 80
0080: 43 2e 7b a9 00 00 7a a8 57 2f 02 54 45 8b 69 54

We already know what the first 5 bytes of the packet are: the 4 byte CSP header and the type indicator. Then, bytes 0x6 to 0x9 are a timestamp stored as a big-endian 32bit integer. Indeed, 0x572f0e5c = 1462701660. Plugging that number into an online epoch converter shows “Sun, 08 May 2016 10:01:00 GMT”. This is more or less when the recording was made (give or take a minute).

The document from GomSpace shows that the last bytes contain data about the last ADS-B beacon received by GOMX-3. The last 4 bytes (bytes 0x88 to 0x8b) contain a timestamp for the reception of the beacon. We see that 0x572f0254 is “Sun, 08 May 2016 09:09:40 GMT”. Bytes 0x84 to 0x87 contain the altitude in feet of the aircraft transmitting the beacon, stored as a big-endian 32bit unsigned integer. We see that 0x00007aa8 = 31400ft. Bytes 0x7c to 0x83 contain the latitude and longitude of the aircraft, stored as two big-endian floats. We can use a hex to float converter to see that 0xc214ab80 = -37.167480 and 0x432e7ba9 = 174.483047. Therefore, the aircraft was flying near Auckland, New Zealand. Bytes 0x78 to 0x7b store the ICAO 24bit address of the aircraft as a big-endian 32bit unsigned integer. In this case, the address is 0x7c6b11. We can use an online database to see that the tail number of the corresponding aircraft is VH-VFN. This is an Airbus A320-232 operated by JetStar Airways. You can see some more data and photos of this aircraft in

This ADS-B data almost makes sense. In FlightAware, we can see that VH-VFN covered the flight JST205 between Syndey YSSY and Auckland NZAA. Moreover, the flightlog shows that at 09:10:05 UTC, the aircraft was at -37.1644ºN, 173.1378ºE and 30600ft descending at 2133ft/min on its way down to Auckland. Also, GOMX-3 was over New Zealand according to my TLEs.

However, note that there is more than 1º of difference in the longitude value. Apparently, position reporting in ADS-B is not as easy as one might first think. Two beacons have to be received to get the full information to compute the position of the aircraft. Probably GOMX-3 didn’t get the two beacons or somehow messed up the calculation. Another curious thing is that GOMX-3 didn’t manage to receive any other ADS-B beacons in the 50 minutes that it took it to orbit from New Zealand to Spain.

According to the documentation from GomSpace, the remaining fields of OBC beacons of type 0 are lots of voltages, currents and a couple of temperatures. These are not so interesting unless one has good knowledge of the satellite subsystems, and it’s not easy to check if the information is being interpreted correctly. The temperatures are stored as integers, so they probably need some unknown conversion formula.

The next two OBC beacons of type 0 came with the ADS-B fields set to zero. After that, I got three more beacons having valid data for aircraft flying over Spain:

The timestamp of each of these packets was just one second later than the ADS-B data timestamp. This suggest that GOMX-3 was receiving ADS-B beacons almost continuously when orbiting over Spain.

The beacons from the ADCS unit contain several temperatures and measurements from the sun sensors, gyroscopes and magnetic sensors. These are not so easy to interpret and check without good knowledge of these systems.

As usual, the data I’ve used in this post is in gist. I’ve also uploaded the FM demodulated baseband data. The software I’ve used is in my gr-ax100 fork.


  1. Dani,

    Thanks for providing the more detailed information, interpretation and the demodulated baseband file.
    Appreciate your sharing the work with us.



Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.