Skip to main content
This tutorial walks through scenarioAttitudeFeedback.py, which demonstrates how to detumble a 6-DOF spacecraft in Earth orbit using a closed-loop MRP (Modified Rodrigues Parameters) attitude control law.

What this scenario demonstrates

  • Setting up a 6-DOF spacecraft with mass and inertia properties
  • Simulating an external disturbance torque via extForceTorque
  • Wiring navigation, guidance, error tracking, and control modules through messages
  • Configuring the classic MRP feedback controller (mrpFeedback)
  • Optionally adding integral feedback to reject unknown constant disturbances
  • Creating and writing VehicleConfigMsg in Python

Modules and message flow

spacecraft  →  scStateOutMsg

            simpleNav.scStateInMsg
            simpleNav  →  attOutMsg

                    attTrackingError.attNavInMsg
inertial3D  →  attRefOutMsg

            attTrackingError.attRefInMsg
            attTrackingError  →  attGuidOutMsg

                          mrpFeedback.guidInMsg
            VehicleConfigMsg  →  mrpFeedback.vehConfigInMsg
            mrpFeedback  →  cmdTorqueOutMsg

                    extForceTorque.cmdTorqueInMsg

Key modules

ModulePackageRole
spacecraft.SpacecraftBasilisk.simulation6-DOF rigid body dynamics
extForceTorque.ExtForceTorqueBasilisk.simulationApplies control or disturbance torques
simpleNav.SimpleNavBasilisk.simulationPasses through truth attitude and rate
inertial3D.inertial3DBasilisk.fswAlgorithmsInertial reference frame definition
attTrackingError.attTrackingErrorBasilisk.fswAlgorithmsComputes attitude tracking error
mrpFeedback.mrpFeedbackBasilisk.fswAlgorithmsClassic MRP feedback control law

Running the scenario

python3 scenarioAttitudeFeedback.py
The run() function signature:
run(
    True,   # show_plots
    False,  # useUnmodeledTorque - adds a constant external disturbance
    False,  # useIntGain         - enables integral feedback term
    False,  # useKnownTorque     - feed-forwards the external torque
    False,  # useCMsg            - uses C-wrapped message instead of C++
)

Step-by-step walkthrough

1

Create the simulation container

from Basilisk.utilities import SimulationBaseClass, macros

simTaskName = "simTask"
simProcessName = "simProcess"

scSim = SimulationBaseClass.SimBaseClass()
simulationTime = macros.min2nano(10.)  # [ns] - 10-minute run

dynProcess = scSim.CreateNewProcess(simProcessName)
simulationTimeStep = macros.sec2nano(.1)  # [ns] - 0.1-second step
dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
2

Configure spacecraft mass and inertia

Unlike the basic orbit scenario, attitude control requires realistic mass properties.
from Basilisk.simulation import spacecraft
from Basilisk.utilities import unitTestSupport

scObject = spacecraft.Spacecraft()
scObject.ModelTag = "spacecraftBody"

I = [900., 0., 0.,
     0., 800., 0.,
     0., 0., 600.]  # [kg*m^2] - inertia tensor about B, in B-frame

scObject.hub.mHub = 750.0  # [kg] - spacecraft mass
scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]]  # [m] - CoM offset from B
scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I)

scSim.AddModelToTask(simTaskName, scObject)
3

Add Earth gravity

from Basilisk.utilities import simIncludeGravBody

gravFactory = simIncludeGravBody.gravBodyFactory()
earth = gravFactory.createEarth()
earth.isCentralBody = True
mu = earth.mu

gravFactory.addBodiesTo(scObject)
4

Add the external force/torque effector

extForceTorque receives the control torque through its input message. You can also inject a constant disturbance torque directly on the module.
from Basilisk.simulation import extForceTorque

extFTObject = extForceTorque.ExtForceTorque()
extFTObject.ModelTag = "externalDisturbance"

# Optionally inject an unmodeled disturbance torque [N*m]
if useUnmodeledTorque:
    extFTObject.extTorquePntB_B = [[0.25], [-0.25], [0.1]]

scObject.addDynamicEffector(extFTObject)
scSim.AddModelToTask(simTaskName, extFTObject)
5

Set up the navigation and FSW modules

Wire together the navigation and flight software chain.
from Basilisk.simulation import simpleNav
from Basilisk.fswAlgorithms import inertial3D, attTrackingError, mrpFeedback

# Navigation: passes spacecraft state truth as a navigation message
sNavObject = simpleNav.SimpleNav()
sNavObject.ModelTag = "SimpleNavigation"
scSim.AddModelToTask(simTaskName, sNavObject)

# Reference: inertial pointing at sigma_R0N = [0, 0, 0]
inertial3DObj = inertial3D.inertial3D()
inertial3DObj.ModelTag = "inertial3D"
inertial3DObj.sigma_R0N = [0., 0., 0.]
scSim.AddModelToTask(simTaskName, inertial3DObj)

# Tracking error: computes sigma_BR and omega_BR
attError = attTrackingError.attTrackingError()
attError.ModelTag = "attErrorInertial3D"
scSim.AddModelToTask(simTaskName, attError)

# MRP Feedback controller
mrpControl = mrpFeedback.mrpFeedback()
mrpControl.ModelTag = "mrpFeedback"
mrpControl.K = 3.5    # proportional gain
mrpControl.P = 30.0   # derivative gain
if useIntGain:
    mrpControl.Ki = 0.0002   # integral gain
else:
    mrpControl.Ki = -1       # negative value disables integral term
mrpControl.integralLimit = 2. / mrpControl.Ki * 0.1
if useKnownTorque:
    mrpControl.knownTorquePntB_B = [0.25, -0.25, 0.1]  # [N*m] feed-forward
scSim.AddModelToTask(simTaskName, mrpControl)
6

Create the vehicle configuration message

mrpFeedback requires the spacecraft inertia tensor to compute the control torque.
from Basilisk.architecture import messaging

configData = messaging.VehicleConfigMsgPayload(ISCPntB_B=I)
configDataMsg = messaging.VehicleConfigMsg()
configDataMsg.write(configData)
7

Connect all messages

Each module’s input message subscribes to another module’s output message.
sNavObject.scStateInMsg.subscribeTo(scObject.scStateOutMsg)
attError.attNavInMsg.subscribeTo(sNavObject.attOutMsg)
attError.attRefInMsg.subscribeTo(inertial3DObj.attRefOutMsg)
mrpControl.guidInMsg.subscribeTo(attError.attGuidOutMsg)
mrpControl.vehConfigInMsg.subscribeTo(configDataMsg)
extFTObject.cmdTorqueInMsg.subscribeTo(mrpControl.cmdTorqueOutMsg)
The subscribeTo() call establishes a zero-copy link between modules: no data is copied at connection time. The output message buffer is read directly by the subscribed module at each time step.
8

Set initial spacecraft state

Set both the orbital position and the initial attitude and angular rate.
from Basilisk.utilities import orbitalMotion

oe = orbitalMotion.ClassicElements()
oe.a = 10000000.0  # [m]   - 10,000 km semi-major axis
oe.e = 0.01
oe.i = 33.3 * macros.D2R   # [rad]
oe.Omega = 48.2 * macros.D2R
oe.omega = 347.8 * macros.D2R
oe.f = 85.3 * macros.D2R
rN, vN = orbitalMotion.elem2rv(mu, oe)

scObject.hub.r_CN_NInit = rN              # [m]   - inertial position
scObject.hub.v_CN_NInit = vN              # [m/s] - inertial velocity
scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]]    # MRP initial attitude
scObject.hub.omega_BN_BInit = [[0.001], [-0.01], [0.03]]  # [rad/s] initial rate
9

Set up data logging, initialize, and run

from Basilisk.utilities import unitTestSupport

numDataPoints = 100
samplingTime = unitTestSupport.samplingTime(
    simulationTime, simulationTimeStep, numDataPoints
)
attErrorLog = attError.attGuidOutMsg.recorder(samplingTime)
mrpLog = mrpControl.cmdTorqueOutMsg.recorder(samplingTime)
snLog = sNavObject.scStateInMsg.recorder(samplingTime)

scSim.AddModelToTask(simTaskName, attErrorLog)
scSim.AddModelToTask(simTaskName, mrpLog)
scSim.AddModelToTask(simTaskName, snLog)

scSim.InitializeSimulation()
scSim.ConfigureStopTime(simulationTime)
scSim.ExecuteSimulation()
10

Retrieve results

import matplotlib.pyplot as plt

timeAxis = attErrorLog.times()  # [ns]

# Attitude tracking error (MRP)
sigma_BR = attErrorLog.sigma_BR   # shape (N, 3)
# Rate tracking error
omega_BR_B = attErrorLog.omega_BR_B  # shape (N, 3) [rad/s]
# Commanded control torque
torque = mrpLog.torqueRequestBody  # shape (N, 3) [N*m]
With useUnmodeledTorque=False, the MRP error converges to zero within a few minutes. Adding an integral gain (useIntGain=True) eliminates the steady-state offset caused by the disturbance.

Controller gain guide

ParameterSymbolEffect
Kproportional gainStiffness of the attitude response
Pderivative gainDamping — prevents overshoot
Kiintegral gainEliminates steady-state error from constant torques; set negative to disable
knownTorquePntB_Bfeed-forwardDirectly cancels a known constant torque
Set mrpControl.Ki = -1 (negative) to disable the integral term without changing any other code paths. The implementation checks the sign of Ki internally.

Build docs developers (and LLMs) love