The CCSDS packet structure
CCSDSPy is a Python library for reading tightly packed binary telemetry in the CCSDS (Consultative Committee for Space Data Systems) Space Packet format. This format is used for spacecraft telemetry by NASA, ESA, NOAA, and other space agencies, and is the wire format behind missions such as GOES-R, Europa Clipper, MMS, and PACE.
Before you define fields with PacketField and PacketArray, it helps to understand the packet that those definitions describe. This page walks through the on-the-wire layout of a single Space Packet.
The Space Packet
Every CCSDS Space Packet is made of three contiguous parts:
- A mandatory primary header — exactly 6 octets (48 bits).
- An optional secondary header — variable length, present only when a flag in the primary header says so.
- The user data field — the variable-length payload.
Together, the secondary header (if present) and the user data field form what the standard calls the packet data field — everything after the primary header.
+-----------------------+------------------------+--------------------+
| Primary Header | Secondary Header | User Data |
| (6 octets) | (optional, variable) | (variable) |
+-----------------------+------------------------+--------------------+
|<------------ Packet Data Field ----------->|
CCSDSPy parses the primary header for you. When you define a packet with FixedLength or VariableLength, your PacketField and PacketArray definitions describe the bytes that come after the 6-byte primary header — that is, the secondary header and user data. You do not redefine the primary header fields yourself.
The primary header fields
The mandatory primary header is always 48 bits (6 octets), divided into seven fields. The bit widths below are fixed by the Space Packet Protocol standard and are the values CCSDSPy relies on when parsing.
| Field | Bit width | Description |
|---|---|---|
| Packet version number | 3 | The CCSDS version number. Shall be set to 000. |
| Packet type | 1 | For telemetry (or reporting), set to 0; for a command (or request), set to 1. |
| Secondary header flag | 1 | Indicates the presence or absence of a secondary header. Set to 1 if a secondary header is present, 0 if not. |
| Application Process Identifier (APID) | 11 | Uniquely identifies the sending or receiving application on a space vehicle. |
| Sequence flags | 2 | 00 = first segment, 01 = continuation segment, 10 = last segment, 11 = unsegmented (a single, complete packet). |
| Packet sequence count | 14 | The sequential binary count of each packet for a specific APID, used to order packets. |
| Packet data length | 16 | The length in octets of the remainder of the packet (the packet data field) minus 1. |
These widths sum to 3 + 1 + 1 + 11 + 2 + 14 + 16 = 48 bits, confirming the 6-octet total.
The packet data length field encodes "length minus 1." A field value of 0 means the packet data field is 1 octet long, a value of N means it is N + 1 octets long. CCSDSPy accounts for this off-by-one when it computes packet boundaries, so you rarely need to handle it by hand.
The APID is the field you will use most often in practice. A single binary file frequently interleaves packets from many APIDs. Filter or split your data by APID before loading a definition, so that the field layout you pass to CCSDSPy matches every packet you feed it.
The secondary header
The secondary header is optional and directly follows the primary header. Its presence is signalled by the secondary header flag in the primary header (1 = present). When present, it usually carries a time code, and its exact contents are mission-specific.
In CCSDSPy you describe these bytes the same way you describe user data — as PacketField entries at the start of your definition.
The user data field
The user data field is the variable-length payload that follows the (optional) secondary header. This is the science or housekeeping data your instrument produces, and it is the part you spend most of your time describing with field and array definitions.
How this maps to a CCSDSPy definition
The example below is illustrative only — the field names and widths are made up to show the mechanics, not a real mission packet. The fields listed describe the bytes after the primary header.
import ccsdspy
from ccsdspy import PacketField, PacketArray
# Illustrative example — names and widths are not from a real mission.
pkt = ccsdspy.FixedLength([
# Secondary header (a coarse/fine time code), then user data:
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("mypackets.bin")
A few things to note in the context of the packet structure:
- The list contains no primary-header fields — CCSDSPy strips and interprets those 6 bytes automatically.
data_type="fill"marks padding bits that exist on the wire but carry no value, keeping your field offsets aligned.PacketFielddefines a single scalar value;PacketArraydefines a multi-dimensional grid of equally sized values, witharray_shapeandarray_ordercontrolling its layout.
Use ccsdspy.FixedLength when every packet for a given APID has the same length and field layout. Use ccsdspy.VariableLength when a packet contains an expanding field whose size is determined at runtime (for example, an array whose element count is given by an earlier field).