Source code for scenario_AttGuidMultiSat
#
# ISC License
#
# Copyright (c) 2021, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
r"""
Overview
--------
This script sets up three 6-DOF spacecraft orbiting the Earth. The goal of this scenario is to
#. introduce the flight software class,
#. show how one can change the active flight mode on the run, and
#. discuss how this script takes advantage of the new BSK Sim structure.
The script is found in the folder ``basilisk/examples/MultiSatBskSim/scenariosMultiSat`` and is executed by using::
python3 scenario_AttGuidMultiSat.py
This simulation is based on the :ref:`scenario_BasicOrbitMultiSat` with the addition of flight software modules. It also
takes some cues from :ref:`scenario_AttGuidance`, but with several spacecraft and more possible flight modes.
For simplicity, the script plots only the information related to one of the spacecraft, despite logging the necessary
information for all spacecraft in the simulation.
Custom Dynamics Configurations Instructions
-------------------------------------------
The dynamics modules required for this scenario are identical to those used in :ref:`scenario_BasicOrbitMultiSat`.
Custom FSW Configurations Instructions
--------------------------------------
In this example, all spacecraft inherit the same flight software class defined in :ref:`BSK_MultiSatFsw`. Four flight
modes are implemented through the use of events and are described below:
#. ``standby``: the spacecraft has no attitude requirements.
#. ``inertialPointing``: the spacecraft points at some inertially fixed location.
#. ``sunPointing``: the spacecraft points at the Sun.
#. ``locationPointing``: the spacecraft aims at a ground location on Earth.
The attitude is controlled using a set of four reaction wheels that are set up in :ref:`BSK_MultiSatDynamics`. The
``mrpFeedback`` is used for the control law and ``rwMotorTorque`` interfaces with the reaction wheels. The
``attTrackingError`` module is used with all modes to convert from a reference message to a guidance one.
The events can be set using the ``modeRequest`` flag inside the FSW class. It is crucial that all events call the
``setAllButCurrentEventActivity`` method. This function is called in a way such that all events' activity is made active
except for the current one. Without this command, every event could only be made active once. The method also makes
sure it only affects the events specific to each spacecraft. For more information, see :ref:`SimulationBaseClass`.
No formation flying control is done in this scenario. To see a more complete example which includes formation geometry
control, see :ref:`scenario_StationKeepingMultiSat`.
In this scenario, it is shown how the flight software events are set up, and how to change them on-the-fly.
Illustration of Simulation Results
----------------------------------
Since three spacecraft are simulated, and to prevent excessively busy plots, only the information pertaining to one
spacecraft is plotted per simulation.
::
show_plots = True, numberSpacecraft=3
.. image:: /_images/Scenarios/scenario_AttGuidMultiSat_attitude.svg
:align: center
.. image:: /_images/Scenarios/scenario_AttGuidMultiSat_rate.svg
:align: center
.. image:: /_images/Scenarios/scenario_AttGuidMultiSat_attitude_tracking_error.svg
:align: center
.. image:: /_images/Scenarios/scenario_AttGuidMultiSat_tracking_error_rate.svg
:align: center
.. image:: /_images/Scenarios/scenario_AttGuidMultiSat_rw_motor_torque.svg
:align: center
.. image:: /_images/Scenarios/scenario_AttGuidMultiSat_rw_speeds.svg
:align: center
"""
import copy
# Get current file path
import inspect
import os
import sys
# Import utilities
from Basilisk.utilities import orbitalMotion, macros, vizSupport
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
# Import master classes: simulation base class and scenario base class
sys.path.append(path + '/../')
sys.path.append(path + '/../modelsMultiSat')
sys.path.append(path + '/../plottingMultiSat')
from BSK_MultiSatMasters import BSKSim, BSKScenario
import BSK_EnvironmentEarth, BSK_MultiSatDynamics, BSK_MultiSatFsw
# Import plotting files for your scenario
import BSK_MultiSatPlotting as plt
# Create your own scenario child class
[docs]
class scenario_AttGuidFormationFlying(BSKSim, BSKScenario):
def __init__(self, numberSpacecraft):
super(scenario_AttGuidFormationFlying, self).__init__(numberSpacecraft, fswRate=10, dynRate=10, envRate=10)
self.name = 'scenario_AttGuidFormationFlying'
self.set_EnvModel(BSK_EnvironmentEarth)
self.set_DynModel([BSK_MultiSatDynamics]*self.numberSpacecraft)
self.set_FswModel([BSK_MultiSatFsw]*self.numberSpacecraft)
# declare empty class variables
self.samplingTime = []
self.snTransLog = []
self.snAttLog = []
self.attErrorLog = []
self.rwMotorLog = []
self.rwSpeedLog = []
self.rwLogs = [[] for _ in range(self.numberSpacecraft)]
# declare empty containers for orbital elements
self.oe = []
self.configure_initial_conditions()
self.log_outputs()
# if this scenario is to interface with the BSK Viz, uncomment the saveFile line
DynModelsList = []
rwStateEffectorList = []
for i in range(self.numberSpacecraft):
DynModelsList.append(self.DynModels[i].scObject)
rwStateEffectorList.append(self.DynModels[i].rwStateEffector)
vizSupport.enableUnityVisualization(self, self.DynModels[0].taskName, DynModelsList
# , saveFile=__file__
, rwEffectorList=rwStateEffectorList
)
[docs]
def configure_initial_conditions(self):
EnvModel = self.get_EnvModel()
DynModels = self.get_DynModel()
# Configure Dynamics initial conditions
self.oe.append(orbitalMotion.ClassicElements())
self.oe[0].a = 1.1*EnvModel.planetRadius # meters
self.oe[0].e = 0.01
self.oe[0].i = 45.0 * macros.D2R
self.oe[0].Omega = 48.2 * macros.D2R
self.oe[0].omega = 347.8 * macros.D2R
self.oe[0].f = 85.3 * macros.D2R
rN, vN = orbitalMotion.elem2rv(EnvModel.mu, self.oe[0])
orbitalMotion.rv2elem(EnvModel.mu, rN, vN)
DynModels[0].scObject.hub.r_CN_NInit = rN # m - r_CN_N
DynModels[0].scObject.hub.v_CN_NInit = vN # m/s - v_CN_N
DynModels[0].scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]] # sigma_BN_B
DynModels[0].scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B
# Configure Dynamics initial conditions
self.oe.append(copy.deepcopy(self.oe[0]))
self.oe[1].a = 1.2*EnvModel.planetRadius
self.oe[1].e = 0.1
self.oe[1].i = 30.0 * macros.D2R
rN2, vN2 = orbitalMotion.elem2rv(EnvModel.mu, self.oe[1])
orbitalMotion.rv2elem(EnvModel.mu, rN2, vN2)
DynModels[1].scObject.hub.r_CN_NInit = rN2 # m - r_CN_N
DynModels[1].scObject.hub.v_CN_NInit = vN2 # m/s - v_CN_N
DynModels[1].scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]] # sigma_BN_B
DynModels[1].scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B
# Configure Dynamics initial conditions
self.oe.append(copy.deepcopy(self.oe[0]))
self.oe[2].a = 1.3 * EnvModel.planetRadius
self.oe[2].e = 0.05
self.oe[2].i = 60.0 * macros.D2R
rN3, vN3 = orbitalMotion.elem2rv(EnvModel.mu, self.oe[2])
orbitalMotion.rv2elem(EnvModel.mu, rN3, vN3)
DynModels[2].scObject.hub.r_CN_NInit = rN3 # m - r_CN_N
DynModels[2].scObject.hub.v_CN_NInit = vN3 # m/s - v_CN_N
DynModels[2].scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]] # sigma_BN_B
DynModels[2].scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B
[docs]
def log_outputs(self):
# Process outputs
DynModels = self.get_DynModel()
FswModels = self.get_FswModel()
# Set the sampling time
self.samplingTime = macros.sec2nano(1)
# Loop through every spacecraft
for spacecraft in range(self.numberSpacecraft):
# log the navigation messages
self.snTransLog.append(DynModels[spacecraft].simpleNavObject.transOutMsg.recorder(self.samplingTime))
self.snAttLog.append(DynModels[spacecraft].simpleNavObject.attOutMsg.recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.snTransLog[spacecraft])
self.AddModelToTask(DynModels[spacecraft].taskName, self.snAttLog[spacecraft])
# log the attitude error messages
self.attErrorLog.append(FswModels[spacecraft].attGuidMsg.recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.attErrorLog[spacecraft])
# log the RW torque messages
self.rwMotorLog.append(
FswModels[spacecraft].rwMotorTorque.rwMotorTorqueOutMsg.recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.rwMotorLog[spacecraft])
# log the RW wheel speed information
self.rwSpeedLog.append(DynModels[spacecraft].rwStateEffector.rwSpeedOutMsg.recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.rwSpeedLog[spacecraft])
# log addition RW information (power, etc)
for item in range(DynModels[spacecraft].numRW):
self.rwLogs[spacecraft].append(
DynModels[spacecraft].rwStateEffector.rwOutMsgs[item].recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.rwLogs[spacecraft][item])
[docs]
def pull_outputs(self, show_plots, spacecraftIndex):
# Dynamics process outputs
DynModels = self.get_DynModel()
#
# Retrieve the logged data
#
dataUsReq = self.rwMotorLog[spacecraftIndex].motorTorque
dataSigmaBR = self.attErrorLog[spacecraftIndex].sigma_BR
dataOmegaBR = self.attErrorLog[spacecraftIndex].omega_BR_B
dataSigmaBN = self.snAttLog[spacecraftIndex].sigma_BN
dataOmegaBN_B = self.snAttLog[spacecraftIndex].omega_BN_B
dataOmegaRW = self.rwSpeedLog[spacecraftIndex].wheelSpeeds
dataRW = []
for item in range(DynModels[spacecraftIndex].numRW):
dataRW.append(self.rwLogs[spacecraftIndex][item].u_current)
#
# Plot results
#
plt.clear_all_plots()
timeLineSetMin = self.snTransLog[spacecraftIndex].times() * macros.NANO2MIN
timeLineSetSec = self.snTransLog[spacecraftIndex].times() * macros.NANO2SEC
plt.plot_attitude(timeLineSetMin, dataSigmaBN, 1)
plt.plot_rate(timeLineSetMin, dataOmegaBN_B, 2)
plt.plot_attitude_error(timeLineSetMin, dataSigmaBR, 3)
plt.plot_rate_error(timeLineSetMin, dataOmegaBR, 4)
plt.plot_rw_motor_torque(timeLineSetMin, dataUsReq, dataRW, DynModels[spacecraftIndex].numRW, 5)
plt.plot_rw_speeds(timeLineSetMin, dataOmegaRW, DynModels[spacecraftIndex].numRW, 6)
figureList = {}
if show_plots:
plt.show_all_plots()
else:
fileName = os.path.basename(os.path.splitext(__file__)[0])
figureNames = ["attitude", "rate", "attitude_tracking_error", "tracking_error_rate",
"rw_motor_torque", "rw_speeds"]
figureList = plt.save_all_plots(fileName, figureNames)
# close the plots being saved off to avoid over-writing old and new figures
plt.clear_all_plots()
return figureList
def runScenario(scenario):
# Configure FSW mode
scenario.FSWModels[0].modeRequest = "sunPointing"
scenario.FSWModels[1].modeRequest = "standby"
scenario.FSWModels[2].modeRequest = "locationPointing"
# Initialize simulation
scenario.InitializeSimulation()
# Configure run time and execute simulation
simulationTime = macros.hour2nano(1.)
scenario.ConfigureStopTime(simulationTime)
scenario.ExecuteSimulation()
# change flight mode on selected spacecraft
scenario.FSWModels[1].modeRequest = "sunPointing"
scenario.FSWModels[2].modeRequest = "inertialPointing"
scenario.ConfigureStopTime(2*simulationTime)
scenario.ExecuteSimulation()
[docs]
def run(show_plots, numberSpacecraft):
"""
The scenarios can be run with the followings setups parameters:
Args:
show_plots (bool): Determines if the script should display plots
numberSpacecraft (int): Number of spacecraft in the simulation
"""
# Configure a scenario in the base simulation
TheScenario = scenario_AttGuidFormationFlying(numberSpacecraft)
runScenario(TheScenario)
figureList = TheScenario.pull_outputs(show_plots, 2)
return figureList
if __name__ == "__main__":
run(show_plots=True,
numberSpacecraft=3
)