From real missions
CCSDSPy describes itself as an "I/O interface and utilities for CCSDS binary spacecraft data in Python," and its GitHub repository description notes that it is a "library used in flight missions at NASA, NOAA, and SWRI." The project's Used By section lists the following missions and organizations:
GOES-R, Europa Clipper, MMS, PACE, HERMES, CSA, PUNCH, SPHEREx, ELFIN, and PADRE
These span weather satellites (GOES-R, operated for NOAA), a flagship planetary mission (Europa Clipper), heliophysics constellations (MMS, ELFIN, PUNCH, HERMES, PADRE), an ocean-and-atmosphere observatory (PACE), and an all-sky infrared survey (SPHEREx). What they share is that their instruments emit telemetry as CCSDS Space Packets: a 6-byte primary header followed by a mission-defined data field. CCSDSPy's job is to turn that packed binary into NumPy arrays.
This page walks through the two telemetry patterns you meet most often — housekeeping and science — using the same API those missions rely on.
The mission list above is quoted directly from the CCSDSPy README and documentation index. It indicates that these projects use the library; it does not mean CCSDSPy is the only or official ground system for any of them. Where this page shows concrete field names and bytes, those are illustrative and were written for the example — they are not pulled from any mission's real telemetry dictionary.
Why CCSDS, and why a library
Every packet on these missions begins with the standard CCSDS primary header, which carries the application process identifier (APID) that identifies the source, a sequence count, and the packet length. CCSDSPy decodes that header for you, so your packet definition only needs to describe the data field — the mission-specific payload after the header.
You describe that payload as an ordered list of fields. Two field classes cover almost everything:
| Class | Use for |
|---|---|
PacketField | A single scalar value (a counter, a flag, a voltage, a mode). |
PacketArray | A repeated value: a sensor grid, a waveform, a buffer of samples. |
You then choose a packet class based on whether the layout is constant:
| Class | Use when |
|---|---|
ccsdspy.FixedLength | Every packet of a given APID has the same byte layout. |
ccsdspy.VariableLength | One field grows or shrinks from packet to packet. |
Example 1 — Housekeeping telemetry (fixed length)
Housekeeping (HK) packets report the health of the spacecraft or instrument:
clock values, an operating mode, bus voltages, temperatures. They almost always
have a fixed layout, which makes FixedLength the right tool.
The definition below is illustrative but uses only real CCSDSPy data types
(uint, int, fill) and the same construction used throughout the official
User Guide.
import ccsdspy
from ccsdspy import PacketField, PacketArray
# Illustrative HK layout — field names and sizes are made up for this example.
hk = ccsdspy.FixedLength([
PacketField(name="SHCOARSE", data_type="uint", bit_length=32), # coarse time
PacketField(name="SHFINE", data_type="uint", bit_length=20), # fine time
PacketField(name="OPMODE", data_type="uint", bit_length=3), # mode enum
PacketField(name="SPACER", data_type="fill", bit_length=1), # unused bit
PacketField(name="VOLTAGE", data_type="int", bit_length=8), # signed bus V
PacketArray(
name="SENSOR_GRID",
data_type="uint",
bit_length=16,
array_shape=(32, 32),
array_order="C",
),
])
result = hk.load("housekeeping.tlm", include_primary_header=True)
load() returns a dictionary that maps each field name to a NumPy array, with
one row per packet found in the file. The SENSOR_GRID entry is a stack of
32×32 arrays. Because include_primary_header=True, the decoded header fields
are added too:
print(result.keys())
# dict_keys(['CCSDS_VERSION_NUMBER', 'CCSDS_PACKET_TYPE', 'CCSDS_SECONDARY_FLAG',
# 'CCSDS_SEQUENCE_FLAG', 'CCSDS_SEQUENCE_COUNT', 'CCSDS_PACKET_LENGTH',
# 'SHCOARSE', 'SHFINE', 'OPMODE', 'SPACER', 'VOLTAGE', 'SENSOR_GRID'])
print(result["OPMODE"][:5]) # operating mode of the first five packets
print(result["VOLTAGE"].mean()) # average bus voltage across the file
The fill data type marks bits that exist in the wire format but that you do not
want decoded into a usable value — alignment padding, reserved fields, spare
bits. CCSDSPy still accounts for their width when computing offsets, so the
fields after them line up correctly.
Set include_primary_header=True whenever you need the APID, sequence count, or
packet length. Splitting a mixed file by CCSDS_SEQUENCE_COUNT is the easiest
way to detect dropped packets in a downlink.
Example 2 — Science telemetry (variable length)
Science packets often carry a payload whose size changes from packet to packet —
a burst of samples, a compressed image strip, an event list. For these you use
VariableLength and declare the changing field as a PacketArray. CCSDSPy uses
the packet-length value in the primary header to work out the true size of each
packet as it decodes.
There are two ways to size the variable field.
Expanding field
An expanding array consumes whatever bytes remain in the packet after the
other fields are accounted for. Set array_shape="expand".
import ccsdspy
from ccsdspy import PacketField, PacketArray
# Illustrative science layout with one expanding payload.
sci = ccsdspy.VariableLength([
PacketField(name="SHCOARSE", data_type="uint", bit_length=32),
PacketArray(
name="DATA",
data_type="uint",
bit_length=16,
array_shape="expand",
),
PacketField(name="CHECKSUM", data_type="uint", bit_length=16),
])
result = sci.load("science.tlm")
Only one field per definition may use array_shape="expand".
Field sized by another field
The second form links the array's element count to a counter carried earlier in
the same packet. Set array_shape to the name of that counter field.
CCSDSPy reads the counter first, then reads that many elements. You may have
several such arrays in one packet — the only rule is that each counter must
appear before the array that refers to it.
import ccsdspy
from ccsdspy import PacketField, PacketArray
sci = ccsdspy.VariableLength([
PacketField(name="SHCOARSE", data_type="uint", bit_length=32),
PacketField(name="data1_len", data_type="uint", bit_length=8),
PacketArray(
name="data1",
data_type="uint",
bit_length=16,
array_shape="data1_len",
),
PacketField(name="data2_len", data_type="uint", bit_length=8),
PacketArray(
name="data2",
data_type="uint",
bit_length=16,
array_shape="data2_len",
),
PacketField(name="checksum", data_type="uint", bit_length=16),
])
result = sci.load("science.tlm")
Variable-length decoding is more involved than fixed-length decoding, so it is
slower. If every packet of a given APID really is the same length, prefer
FixedLength. Reach for VariableLength only when the layout genuinely varies.
Defining packets from a CSV file
On a real mission the telemetry database often lives in spreadsheets, not Python source. CCSDSPy can build a packet definition straight from a CSV file, which keeps the layout under version control alongside the rest of the data dictionary:
import ccsdspy
pkt = ccsdspy.FixedLength.from_file("packet_definition.csv")
The same from_file() syntax works for VariableLength. The first row of the
CSV is a header naming the columns. There are two supported layouts.
Three-column (basic). Columns are name, data_type, bit_length. Bit
offsets are computed automatically from the field order:
name,data_type,bit_length
SHCOARSE,uint,32
SHFINE,uint,20
OPMODE,uint,3
VOLTAGE,int,8
Four-column (extended). Add a bit_offset column to place fields
explicitly, which lets you skip over regions of the packet:
name,data_type,bit_length,bit_offset
SHCOARSE,uint,32,0
SHFINE,uint,20,32
VOLTAGE,int,8,64
The extended (four-column) layout is not supported for variable-length
packets: bit_offset cannot be specified when one field changes size. Use the
three-column layout, or define variable-length packets in Python.
Field reference
The data types used above are part of the documented CCSDSPy field model.
CCSDSPy accepts five data_type values in total — the examples on this page use
uint, int, and fill:
data_type | Meaning |
|---|---|
uint | Unsigned integer. |
int | Signed integer. |
float | IEEE floating-point value. |
str | String. |
fill | Padding or reserved bits; counted for offsets but not decoded. |
PacketArray argument | Purpose |
|---|---|
array_shape=(r, c) | Fixed multi-dimensional array of the given shape. |
array_shape="expand" | Variable array that fills the rest of the packet. |
array_shape="name" | Variable array sized by the value of the field called name. |
array_order | "C" (row-major) or "F" (column-major) for fixed N-D arrays. |