Source code for scenarioGroundLocationImaging

#
#  ISC License
#
#  Copyright (c) 2016, 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 scenario demonstrates imaging a target on the surface of the Earth based on access and attitude error requirements.
Simulated data generated from this image is stored in an on-board storage device and then downlinked to a ground
station.

The script is found in the folder ``basilisk/examples`` and executed by using::

    python3 scenarioGroundLocationImaging.py

The simulation uses :ref:`groundLocation` to create an output message with the desired ground target's inertial
position.  This is fed to the 2D pointing module :ref:`locationPointing` which directs the 3rd body axis to point
towards the target. Once the target is accessible per the :ref:`groundLocation` class' ``accessOutMsg`` and below the
prescribed attitude error from the :ref:`locationPointing` class' ``attGuidOutMsg``, the
:ref:`simpleInstrumentController` sends a ``deviceStatusOutMsg`` to the :ref:`simpleInstrument` to turn the imager on
for a single time step. The :ref:`simpleInstrument` sends this data to a :ref:`partitionedStorageUnit`. The data is
then downlinked using a :ref:`spaceToGroundTransmitter` once a ground station represented by another
:ref:`groundLocation` class is accessible.

A reset of the :ref:`groundLocation` and :ref:`simpleInstrumentController` for the purposes of switching to a new target
after the first one is imaged is also demonstrated.

Illustration of Simulation Results
----------------------------------

::

    show_plots = True

The following plots illustrate the 2D pointing error, access data, image commands, and level of the storage unit.

.. image:: /_images/Scenarios/scenarioGroundLocationImaging1.svg
   :align: center

.. image:: /_images/Scenarios/scenarioGroundLocationImaging2.svg
   :align: center

.. image:: /_images/Scenarios/scenarioGroundLocationImaging3.svg
   :align: center

.. image:: /_images/Scenarios/scenarioGroundLocationImaging4.svg
   :align: center

"""

#
# Basilisk Scenario Script and Integrated Test
#
# Purpose:  Integrated test of the spacecraft(), extForceTorque(), simpleNav(), locationPoint(), groundLocation(),
#           simpleInstrumentController(), simpleInstrument(), partitionedStorageUnit(), and spaceToGroundTransmitter()
#           modules.  Will point a spacecraft axis at two Earth fixed locations and downlink the associated data.
# Author:   Adam Herrmann
# Creation Date:  May 11, 2021
#

import os

import matplotlib.pyplot as plt
import numpy as np

# import message declarations
from Basilisk.architecture import messaging
from Basilisk.fswAlgorithms import locationPointing

# import FSW Algorithm related support
from Basilisk.fswAlgorithms import mrpFeedback
from Basilisk.fswAlgorithms import simpleInstrumentController
from Basilisk.simulation import extForceTorque
from Basilisk.simulation import groundLocation
from Basilisk.simulation import partitionedStorageUnit
from Basilisk.simulation import simpleInstrument
from Basilisk.simulation import simpleNav
from Basilisk.simulation import spaceToGroundTransmitter

# import simulation related support
from Basilisk.simulation import spacecraft

# import general simulation support files
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import astroFunctions
from Basilisk.utilities import macros
from Basilisk.utilities import orbitalMotion
from Basilisk.utilities import simIncludeGravBody
from Basilisk.utilities import (
    unitTestSupport,
)  # general support file with common unit test functions

# attempt to import vizard
from Basilisk.utilities import vizSupport

try:
    from Basilisk.simulation import vizInterface

except ImportError:
    pass

# The path to the location of Basilisk
# Used to get the location of supporting data.
from Basilisk import __path__

bskPath = __path__[0]
fileName = os.path.basename(os.path.splitext(__file__)[0])


# Plotting functions
[docs]def plot_attitude_error(timeLineSet, dataSigmaBR): """Plot the attitude result.""" plt.figure(1) fig = plt.gcf() ax = fig.gca() vectorData = dataSigmaBR sNorm = np.array([np.linalg.norm(v) for v in vectorData]) plt.plot( timeLineSet, sNorm, color=unitTestSupport.getLineColor(1, 3), ) plt.xlabel("Time [min]") plt.ylabel(r"Attitude Error Norm $|\sigma_{B/R}|$") ax.set_yscale("log") return
def plot_data_levels(timeLineSet, storageLevel, storedData): plt.figure(4) plt.plot( timeLineSet, storageLevel / 8e3, label="Data Unit Total Storage Level (KB)" ) plt.plot(timeLineSet, storedData[:, 0] / 8e3, label="Boulder Partition Level (KB)") plt.plot(timeLineSet, storedData[:, 1] / 8e3, label="Santiago Partition Level (KB)") plt.xlabel("Time (min)") plt.ylabel("Data Stored (KB)") plt.grid(True) plt.legend() return def plot_device_status(timeLineSet, deviceStatus): plt.figure(3) plt.plot(timeLineSet, deviceStatus) plt.xlabel("Time [min]") plt.ylabel("Device Status") return def plot_access(timeLineSet, hasAccess): plt.figure(2) plt.plot(timeLineSet, hasAccess) plt.xlabel("Time [min]") plt.ylabel("Imaging Target Access") return
[docs]def run(show_plots): """ The scenarios can be run with the followings setups parameters: Args: show_plots (bool): Determines if the script should display plots """ # Create simulation variable names simTaskName = "simTask" simProcessName = "simProcess" # Create a sim module as an empty container scSim = SimulationBaseClass.SimBaseClass() # set the simulation time variable used later on simulationTime = macros.min2nano(20.0) # # create the simulation process # dynProcess = scSim.CreateNewProcess(simProcessName) # create the dynamics task and specify the integration update time simulationTimeStep = macros.sec2nano(1.0) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # # setup the simulation tasks/objects # # initialize spacecraft object and set properties scObject = spacecraft.Spacecraft() scObject.ModelTag = "bsk-Sat" # define the simulation inertia I = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0] scObject.hub.mHub = 750.0 # kg - spacecraft mass scObject.hub.r_BcB_B = [ [0.0], [0.0], [0.0], ] # m - position vector of body-fixed point B relative to CM scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) # add spacecraft object to the simulation process scSim.AddModelToTask(simTaskName, scObject, ModelPriority=100) # clear prior gravitational body and SPICE setup definitions gravFactory = simIncludeGravBody.gravBodyFactory() # setup Earth Gravity Body earth = gravFactory.createEarth() earth.isCentralBody = True # ensure this is the central gravitational body mu = earth.mu # attach gravity model to spacecraft gravFactory.addBodiesTo(scObject) # # initialize Spacecraft States with initialization variables # # setup the orbit using classical orbit elements oe = orbitalMotion.ClassicElements() oe.a = (6378 + 600) * 1000.0 # meters oe.e = 0.01 oe.i = 63.3 * macros.D2R oe.Omega = 88.2 * macros.D2R oe.omega = 347.8 * macros.D2R oe.f = 135.3 * macros.D2R rN, vN = orbitalMotion.elem2rv(mu, oe) scObject.hub.r_CN_NInit = rN # m - r_CN_N scObject.hub.v_CN_NInit = vN # m/s - v_CN_N scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]] # sigma_BN_B scObject.hub.omega_BN_BInit = [[0.001], [-0.01], [0.03]] # rad/s - omega_BN_B # setup extForceTorque module # the control torque is read in through the messaging system extFTObject = extForceTorque.ExtForceTorque() extFTObject.ModelTag = "externalDisturbance" # use the input flag to determine which external torque should be applied # Note that all variables are initialized to zero. Thus, not setting this # vector would leave it's components all zero for the simulation. scObject.addDynamicEffector(extFTObject) scSim.AddModelToTask(simTaskName, extFTObject, ModelPriority=90) # add the simple Navigation sensor module. This sets the SC attitude, rate, position # velocity navigation message sNavObject = simpleNav.SimpleNav() sNavObject.ModelTag = "SimpleNavigation" scSim.AddModelToTask(simTaskName, sNavObject, ModelPriority=100) sNavObject.scStateInMsg.subscribeTo(scObject.scStateOutMsg) # Create the initial imaging target imagingTarget = groundLocation.GroundLocation() imagingTarget.ModelTag = "ImagingTarget" imagingTarget.planetRadius = astroFunctions.E_radius * 1e3 imagingTarget.specifyLocation(np.radians(40.009971), np.radians(-105.243895), 1624) imagingTarget.minimumElevation = np.radians(10.0) imagingTarget.maximumRange = 1e9 imagingTarget.addSpacecraftToModel(scObject.scStateOutMsg) scSim.AddModelToTask(simTaskName, imagingTarget, ModelPriority=100) # Create a ground station in Singapore singaporeStation = groundLocation.GroundLocation() singaporeStation.ModelTag = "SingaporeStation" singaporeStation.planetRadius = astroFunctions.E_radius * 1e3 singaporeStation.specifyLocation(np.radians(1.3521), np.radians(103.8198), 15) singaporeStation.minimumElevation = np.radians(5.0) singaporeStation.maximumRange = 1e9 singaporeStation.addSpacecraftToModel(scObject.scStateOutMsg) scSim.AddModelToTask(simTaskName, singaporeStation, ModelPriority=100) # Create a "transmitter" transmitter = spaceToGroundTransmitter.SpaceToGroundTransmitter() transmitter.ModelTag = "transmitter" transmitter.nodeBaudRate = -1e5 # baud transmitter.packetSize = -8e6 # bits transmitter.numBuffers = 2 transmitter.addAccessMsgToTransmitter(singaporeStation.accessOutMsgs[-1]) scSim.AddModelToTask(simTaskName, transmitter, ModelPriority=99) # Create an instrument instrument = simpleInstrument.SimpleInstrument() instrument.ModelTag = "instrument1" instrument.nodeBaudRate = ( 8e6 # baud, assumes the instantaneous writing of a single image ) instrument.nodeDataName = "boulder" scSim.AddModelToTask(simTaskName, instrument, ModelPriority=90) # Create a partitionedStorageUnit and attach the instrument to it dataMonitor = partitionedStorageUnit.PartitionedStorageUnit() dataMonitor.ModelTag = "dataMonitor" dataMonitor.storageCapacity = 0.25 * 8e7 # bits dataMonitor.addDataNodeToModel(instrument.nodeDataOutMsg) dataMonitor.addDataNodeToModel(transmitter.nodeDataOutMsg) dataMonitor.addPartition("boulder") dataMonitor.addPartition("santiago") scSim.AddModelToTask(simTaskName, dataMonitor, ModelPriority=89) transmitter.addStorageUnitToTransmitter(dataMonitor.storageUnitDataOutMsg) # # setup the FSW algorithm tasks # # setup Boulder pointing guidance module locPoint = locationPointing.locationPointing() locPoint.ModelTag = "locPoint" scSim.AddModelToTask(simTaskName, locPoint, 99) locPoint.pHat_B = [0, 0, 1] locPoint.scAttInMsg.subscribeTo(sNavObject.attOutMsg) locPoint.scTransInMsg.subscribeTo(sNavObject.transOutMsg) locPoint.locationInMsg.subscribeTo(imagingTarget.currentGroundStateOutMsg) # setup the MRP Feedback control module mrpControl = mrpFeedback.mrpFeedback() mrpControl.ModelTag = "mrpFeedback" scSim.AddModelToTask(simTaskName, mrpControl, ModelPriority=98) mrpControl.guidInMsg.subscribeTo(locPoint.attGuidOutMsg) mrpControl.K = 5.5 mrpControl.Ki = -1 # make value negative to turn off integral feedback mrpControl.P = 30.0 mrpControl.integralLimit = 2.0 / mrpControl.Ki * 0.1 # connect torque command to external torque effector extFTObject.cmdTorqueInMsg.subscribeTo(mrpControl.cmdTorqueOutMsg) # setup the simpleInstrumentController module simpleInsControl = simpleInstrumentController.simpleInstrumentController() simpleInsControl.attErrTolerance = 0.1 simpleInsControl.ModelTag = "instrumentController" simpleInsControl.attGuidInMsg.subscribeTo(locPoint.attGuidOutMsg) simpleInsControl.locationAccessInMsg.subscribeTo(imagingTarget.accessOutMsgs[-1]) scSim.AddModelToTask(simTaskName, simpleInsControl, ModelPriority=97) instrument.nodeStatusInMsg.subscribeTo(simpleInsControl.deviceCmdOutMsg) # # Setup data logging before the simulation is initialized # deviceLog = simpleInsControl.deviceCmdOutMsg.recorder() mrpLog = mrpControl.cmdTorqueOutMsg.recorder() attErrLog = locPoint.attGuidOutMsg.recorder() snAttLog = sNavObject.attOutMsg.recorder() snTransLog = sNavObject.transOutMsg.recorder() dataMonLog = dataMonitor.storageUnitDataOutMsg.recorder() transmitterLog = transmitter.nodeDataOutMsg.recorder() locationLog = imagingTarget.accessOutMsgs[-1].recorder() scSim.AddModelToTask(simTaskName, dataMonLog) scSim.AddModelToTask(simTaskName, mrpLog) scSim.AddModelToTask(simTaskName, attErrLog) scSim.AddModelToTask(simTaskName, snAttLog) scSim.AddModelToTask(simTaskName, snTransLog) scSim.AddModelToTask(simTaskName, deviceLog) scSim.AddModelToTask(simTaskName, transmitterLog) scSim.AddModelToTask(simTaskName, locationLog) # # create simulation messages # # create the FSW vehicle configuration message vehicleConfigOut = messaging.VehicleConfigMsgPayload() vehicleConfigOut.ISCPntB_B = ( I # use the same inertia in the FSW algorithm as in the simulation ) configDataMsg = messaging.VehicleConfigMsg().write(vehicleConfigOut) mrpControl.vehConfigInMsg.subscribeTo(configDataMsg) # # setup Vizard visualization elements # if vizSupport.vizFound: genericSensorHUD = vizInterface.GenericSensor() genericSensorHUD.r_SB_B = [0.0, 1.0, 1.0] genericSensorHUD.fieldOfView.push_back( 20.0 * macros.D2R ) # single value means a conic sensor genericSensorHUD.normalVector = [0.0, 0.0, 1.0] genericSensorHUD.color = vizInterface.IntVector( vizSupport.toRGBA255("red", alpha=0.25) ) genericSensorHUD.label = "genSen1" cmdInMsg = messaging.DeviceCmdMsgReader() cmdInMsg.subscribeTo(simpleInsControl.deviceCmdOutMsg) genericSensorHUD.genericSensorCmdInMsg = cmdInMsg transceiverHUD = vizInterface.Transceiver() transceiverHUD.r_SB_B = [0.23, 0.0, 1.38] transceiverHUD.fieldOfView = 40.0 * macros.D2R transceiverHUD.normalVector = [-1.0, 0.0, 1.0] transceiverHUD.color = vizInterface.IntVector(vizSupport.toRGBA255("cyan")) transceiverHUD.label = "antenna" trInMsg = messaging.DataNodeUsageMsgReader() trInMsg.subscribeTo(transmitter.nodeDataOutMsg) transceiverHUD.transceiverStateInMsgs.push_back(trInMsg) hdDevicePanel = vizInterface.GenericStorage() hdDevicePanel.label = "Main Disk" hdDevicePanel.units = "bits" hdDevicePanel.color = vizInterface.IntVector( vizSupport.toRGBA255("blue") + vizSupport.toRGBA255("red") ) hdDevicePanel.thresholds = vizInterface.IntVector([50]) hdInMsg = messaging.DataStorageStatusMsgReader() hdInMsg.subscribeTo(dataMonitor.storageUnitDataOutMsg) hdDevicePanel.dataStorageStateInMsg = hdInMsg # if this scenario is to interface with the BSK Viz, uncomment the "saveFile" line viz = vizSupport.enableUnityVisualization( scSim, simTaskName, scObject # , saveFile=fileName , genericSensorList=genericSensorHUD, transceiverList=transceiverHUD, genericStorageList=hdDevicePanel, ) # the following command sets Viz settings for the first spacecraft in the simulation vizSupport.setInstrumentGuiSetting( viz, showGenericSensorLabels=True, showTransceiverLabels=True, showGenericStoragePanel=True, ) # Add the Boulder target vizSupport.addLocation( viz, stationName="Boulder Target", parentBodyName=earth.displayName, r_GP_P=unitTestSupport.EigenVector3d2list(imagingTarget.r_LP_P_Init), fieldOfView=np.radians(160.0), color="pink", range=2000.0 * 1000, # meters ) # Add target line to first Boulder vizSupport.createTargetLine(viz, toBodyName="Boulder Target", lineColor="red") # Add the Santiago target vizSupport.addLocation( viz, stationName="Santiago Target", parentBodyName=earth.displayName, r_GP_P=[1761771.6422437236, -5022201.882030934, -3515898.6046771165], fieldOfView=np.radians(160.0), color="pink", range=2000.0 * 1000, # meters ) # Add the Santiago target vizSupport.addLocation( viz, stationName="Singapore Station", parentBodyName=earth.displayName, r_GP_P=unitTestSupport.EigenVector3d2list(singaporeStation.r_LP_P_Init), fieldOfView=np.radians(160.0), color="green", range=2000.0 * 1000, # meters ) viz.settings.spacecraftSizeMultiplier = 1.5 viz.settings.showLocationCommLines = 1 viz.settings.showLocationCones = 1 viz.settings.showLocationLabels = 1 # # initialize Simulation # scSim.InitializeSimulation() # # configure a simulation stop time and execute the simulation run # scSim.ConfigureStopTime(simulationTime) scSim.ExecuteSimulation() # # configure new ground location target in Santiago, Chile # imagingTarget.specifyLocation(np.radians(-33.4489), np.radians(-70.6693), 570) instrument.nodeDataName = "santiago" simpleInsControl.imaged = 0 # update targeting line to point to Santiago and be blue if vizSupport.vizFound: vizSupport.targetLineList[0].lineColor = vizSupport.toRGBA255("blue") vizSupport.targetLineList[0].toBodyName = "Santiago Target" vizSupport.updateTargetLineList(viz) # # configure the new simulation stop time and execute sim # scSim.ConfigureStopTime(simulationTime + 3 * simulationTime) scSim.ExecuteSimulation() # # retrieve the logged data # dataSigmaBR = attErrLog.sigma_BR storageLevel = dataMonLog.storageLevel storedDataName = dataMonLog.storedDataName storedData = dataMonLog.storedData deviceCmd = deviceLog.deviceCmd hasAccess = locationLog.hasAccess np.set_printoptions(precision=16) # # plot the results # timeLineSet = attErrLog.times() * macros.NANO2MIN plt.close("all") # clears out plots from earlier test runs plot_attitude_error(timeLineSet, dataSigmaBR) figureList = {} pltName = fileName + "1" figureList[pltName] = plt.figure(1) plot_access(timeLineSet, hasAccess) pltName = fileName + "2" figureList[pltName] = plt.figure(2) plot_device_status(timeLineSet, deviceCmd) pltName = fileName + "3" figureList[pltName] = plt.figure(3) plot_data_levels(timeLineSet, storageLevel, storedData) pltName = fileName + "4" figureList[pltName] = plt.figure(4) if show_plots: plt.show() # close the plots being saved off to avoid over-writing old and new figures plt.close("all") return figureList
# # This statement below ensures that the unit test scrip can be run as a # stand-along python script # if __name__ == "__main__": run(True) # show_plots