An Attitude Parameter Message (APM) carries a spacecraft's attitude state at a single epoch through one or more logical blocks — quaternion, Euler angle, angular velocity, spin, inertia, and maneuver. It is the attitude-message counterpart to the OPM: a compact snapshot for handing off attitude state or documenting a planned attitude maneuver. The message is defined by the CCSDS 504.0-B-2 Attitude Data Messages standard.
Every APM has a header (version, creation date, originator), metadata (object identity, center body, time system), and a single epoch that applies to every logical block except maneuvers. The attitude information itself lives in up to six repeatable logical blocks, each of which can appear zero or more times. A message is only valid to write or parse if at least one block is present.
Quaternion blocks (QUAT_START/QUAT_STOP) carry the attitude quaternion and an optional time derivative. Euler angle blocks (EULER_START/EULER_STOP) carry the same rotation as a three-angle sequence plus optional angle rates; the rotation sequence (e.g. ZXZ) is stored alongside the angles. Angular velocity blocks (ANGVEL_START/ANGVEL_STOP) carry an angular velocity vector along with the frame it is expressed in. Spin blocks (SPIN_START/SPIN_STOP) describe a spin-stabilized attitude by spin-axis right ascension, declination, phase angle, and spin rate, with an optional nutation description. Inertia blocks (INERTIA_START/INERTIA_STOP) carry the spacecraft's moment-of-inertia tensor. Maneuver blocks (MAN_START/MAN_STOP) describe a planned or executed attitude maneuver as a torque vector over a duration; unlike the other blocks, a maneuver carries its own epoch rather than using the message epoch.
Every quaternion, Euler angle, and angular velocity block declares a pair of reference frames, REF_FRAME_A and REF_FRAME_B, that together define the rotation direction: the block's values transform a vector from frame A to frame B. This A\(\to\)B convention is fixed by CCSDS 504.0-B-2 and does not depend on which frame is inertial or body-fixed — some fixtures put the spacecraft body frame first, others put it second, and the block's own REF_FRAME_A/REF_FRAME_B fields are the only reliable way to tell which.
CCSDS wire values use different units and component ordering than brahe's internal representation. Brahe converts at the KVN/XML/JSON parse and write boundary, so every value returned by the Python and Rust APIs is already in SI units and brahe's native quaternion convention:
The quaternion reordering matters because most quaternion libraries, including brahe's Quaternion, use a scalar-first convention internally while CCSDS 504.0-B-2 fixes the wire order as scalar-last. Use Quaternion.to_vector(scalar_first=False) (Python) or Quaternion::to_vector(false) (Rust) to recover the wire-order [Q1, Q2, Q3, QC] components shown in a KVN file.
Build an APM programmatically by defining a header, epoch, and metadata, then adding one or more logical blocks. The resulting message can be serialized to KVN, XML, or JSON:
importnumpyasnpimportbraheasbhfrombrahe.ccsdsimportAPM,APMAngularVelocity,APMQuaternionStatebh.initialize_eop()# Create a new APM with header infoepoch=bh.Epoch.from_datetime(2024,6,15,0,0,0.0,0.0,bh.TimeSystem.UTC)apm=APM("BRAHE_EXAMPLE","LEO SAT","2024-100A","UTC",epoch,center_name="EARTH")apm.message_id="APM-2024-001"# Attitude quaternion: spacecraft body frame aligned with ICRF (identity rotation)apm.add_quaternion_state(APMQuaternionState("ICRF","SC_BODY_1",bh.Quaternion(1.0,0.0,0.0,0.0)))# Angular velocity: body spinning about its Z axis at Earth's rotation rateapm.add_angular_velocity(APMAngularVelocity("ICRF","SC_BODY_1","SC_BODY_1",np.array([0.0,0.0,bh.OMEGA_EARTH])))print(f"Created APM with {len(apm.quaternion_states)} quaternion block, "f"{len(apm.angular_velocities)} angular velocity block")# Write to KVN stringkvn=apm.to_string("KVN")print(f"\nKVN output ({len(kvn)} chars):")print(kvn)# Write to fileapm.to_file("/tmp/brahe_example_apm.txt","KVN")print("\nWritten to /tmp/brahe_example_apm.txt")# Verify round-tripapm2=APM.from_file("/tmp/brahe_example_apm.txt")print(f"Round-trip: {len(apm2.quaternion_states)} quaternion block, "f"{len(apm2.angular_velocities)} angular velocity block")
usebraheasbh;usebrahe::ccsds::{ADMReferenceFrame,APM,APMAngularVelocity,APMMetadata,APMQuaternionState,CCSDSFormat,CCSDSTimeSystem,};usenalgebra::Vector3;fnmain(){bh::initialize_eop().unwrap();// Create a new APM with header infoletepoch=bh::Epoch::from_datetime(2024,6,15,0,0,0.0,0.0,bh::TimeSystem::UTC);letmetadata=APMMetadata::new("LEO SAT","2024-100A",CCSDSTimeSystem::UTC).with_center_name("EARTH");letmutapm=APM::new("BRAHE_EXAMPLE",metadata,epoch);apm.header.message_id=Some("APM-2024-001".to_string());// Attitude quaternion: spacecraft body frame aligned with ICRF (identity rotation)apm.push_quaternion_state(APMQuaternionState::new(ADMReferenceFrame::parse("ICRF"),ADMReferenceFrame::parse("SC_BODY_1"),bh::Quaternion::new(1.0,0.0,0.0,0.0),));// Angular velocity: body spinning about its Z axis at Earth's rotation rateapm.push_angular_velocity(APMAngularVelocity::new(ADMReferenceFrame::parse("ICRF"),ADMReferenceFrame::parse("SC_BODY_1"),ADMReferenceFrame::parse("SC_BODY_1"),Vector3::new(0.0,0.0,bh::OMEGA_EARTH),));println!("Created APM with {} quaternion block, {} angular velocity block",apm.quaternion_states.len(),apm.angular_velocities.len());// Write to KVN stringletkvn=apm.to_string(CCSDSFormat::KVN).unwrap();println!("\nKVN output ({} chars):",kvn.len());println!("{}",kvn);// Write to fileapm.to_file("/tmp/brahe_example_apm.txt",CCSDSFormat::KVN).unwrap();println!("\nWritten to /tmp/brahe_example_apm.txt");// Verify round-tripletapm2=APM::from_file("/tmp/brahe_example_apm.txt").unwrap();println!("Round-trip: {} quaternion block, {} angular velocity block",apm2.quaternion_states.len(),apm2.angular_velocities.len());}
Writing and re-parsing an APM preserves all header, metadata, and logical-block values. Numeric precision may vary slightly due to floating-point formatting, but values are preserved within the precision of the output format.
Note that this quaternion block has no bracketed unit annotations — quaternion components are dimensionless. Angle-valued blocks such as Euler angle and spin blocks carry [deg] annotations, which brahe strips during parsing.