Skip to main content

Reading your first packets

CCSDSPy is a Python library for reading tightly packed bits in the Consultative Committee for Space Data Systems (CCSDS) format used by many NASA and ESA missions. This page walks through the most common task: decoding a stream of fixed-length telemetry packets into named NumPy arrays.

The pattern is always the same three steps:

  1. Describe the packet body as an ordered list of fields.
  2. Construct a ccsdspy.FixedLength object from that list.
  3. Call .load() on a binary file to get back a dictionary of arrays.
Prerequisites

CCSDSPy requires Python and NumPy. Install it from PyPI:

pip install ccsdspy

Describe the packet layout

Each field in the packet body is declared with a PacketField. A field has a name, a data_type, and a bit_length. Fields are listed in the exact order they appear in the packet, immediately after the primary header.

For repeating data such as an image or a sensor grid, use PacketArray, which adds an array_shape and array_order on top of the PacketField arguments.

The data_type controls how the raw bits are interpreted:

data_typeMeaning
'uint'Unsigned integer
'int'Signed integer
'float'Floating-point value
'str'String
'fill'Padding / spacer bits that you want to skip over
note

bit_length is specified in bits, not bytes, because CCSDS packets are tightly packed and fields routinely cross byte boundaries (for example a 20-bit or 3-bit field). CCSDSPy handles the bit-level unpacking for you.

Build the FixedLength object and load a file

The following example is taken directly from the CCSDSPy User Guide. It defines a packet with several scalar fields and one 32x32 array, then decodes a binary telemetry file.

import ccsdspy
from ccsdspy import PacketField, PacketArray

pkt = ccsdspy.FixedLength([
PacketField(name='SHCOARSE', data_type='uint', bit_length=32),
PacketField(name='SHFINE', data_type='uint', bit_length=20),
PacketField(name='OPMODE', data_type='uint', bit_length=3),
PacketField(name='SPACER', data_type='fill', bit_length=1),
PacketField(name='VOLTAGE', data_type='int', bit_length=8),
PacketArray(
name='SENSOR_GRID',
data_type='uint',
bit_length=16,
array_shape=(32, 32),
array_order='C'
),
])

result = pkt.load('MyCCSDS.tlm')

What .load() returns

The result is returned as a dictionary, containing the field names as keys. The dictionary values are each an ndarray of the interpreted data from every packet in the file. The keys are in the same order as the fields you passed in.

So for the definition above, result has these entries:

KeyNumPy array shapeNotes
SHCOARSE(n_packets,)One value per packet
SHFINE(n_packets,)One value per packet
OPMODE(n_packets,)One value per packet
VOLTAGE(n_packets,)Signed integer
SENSOR_GRID(n_packets, 32, 32)The 2-D array per packet

The SPACER field has data_type='fill', so it is consumed during decoding but is not returned as a key.

Illustrative example

The snippet below is illustrative (it is not part of the official docs) and shows how you might inspect the decoded data once result is in hand:

# Illustrative: explore the decoded arrays.
for name, values in result.items():
print(name, values.shape, values.dtype)

# Access a single field like any NumPy array.
coarse_time = result['SHCOARSE'] # shape (n_packets,)
first_grid = result['SENSOR_GRID'][0] # the 32x32 grid from packet 0

Including the primary header

By default .load() returns only the body fields you defined. To also decode the standard fields of the CCSDS primary header, pass include_primary_header=True:

result = pkt.load('MyCCSDS.tlm', include_primary_header=True)

This adds the following keys to the returned dictionary:

Header fieldDescription
CCSDS_VERSION_NUMBERPacket version number
CCSDS_PACKET_TYPETelemetry (0) or telecommand (1)
CCSDS_SECONDARY_FLAGSecondary header presence flag
CCSDS_APIDApplication process identifier (APID)
CCSDS_SEQUENCE_FLAGGrouping / segmentation flags
CCSDS_SEQUENCE_COUNTPer-APID sequence counter
CCSDS_PACKET_LENGTHPacket data length field

Next steps

When the packet body length is not constant from one packet to the next (for example an expanding field whose size is given by another field), use ccsdspy.VariableLength instead of ccsdspy.FixedLength. It accepts the same PacketField and PacketArray definitions and also returns a dictionary of NumPy arrays from .load().

Sources