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 )