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:
- Describe the packet body as an ordered list of fields.
- Construct a
ccsdspy.FixedLengthobject from that list. - Call
.load()on a binary file to get back a dictionary of arrays.
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_type | Meaning |
|---|---|
'uint' | Unsigned integer |
'int' | Signed integer |
'float' | Floating-point value |
'str' | String |
'fill' | Padding / spacer bits that you want to skip over |
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:
| Key | NumPy array shape | Notes |
|---|---|---|
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.
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 field | Description |
|---|---|
CCSDS_VERSION_NUMBER | Packet version number |
CCSDS_PACKET_TYPE | Telemetry (0) or telecommand (1) |
CCSDS_SECONDARY_FLAG | Secondary header presence flag |
CCSDS_APID | Application process identifier (APID) |
CCSDS_SEQUENCE_FLAG | Grouping / segmentation flags |
CCSDS_SEQUENCE_COUNT | Per-APID sequence counter |
CCSDS_PACKET_LENGTH | Packet 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().