#
# ISC License
#
# Copyright (c) 2022, 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 how to set up a spacecraft with deploying panels. The panel modules
are set up with individual profilers and motors as shown in the following diagram.
.. image:: /_images/static/test_scenario_DeployingPanel.svg
:align: center
The script is found in the folder ``basilisk/examples`` and executed by using::
python3 scenarioDeployingPanel.py
The simulation includes two deploying panels that start undeployed. The first panel deploys fully,
but the second panel deploys off-nominally (to 80%), leading to a reduced power output.
Illustration of Simulation Results
----------------------------------
::
show_plots = True
Five plots are shown. The first two show the body attitude and rate relative to the inertial frame,
demonstrating the dynamic effect of the deploying panels on the system. Because the second panel
fails to fully deploy, the body attitude does not return to the original state.
.. image:: /_images/Scenarios/scenarioDeployingPanel1.svg
:align: center
.. image:: /_images/Scenarios/scenarioDeployingPanel2.svg
:align: center
The next two plots show the panel angles and angle rates. Because the panels are modeled as
rigid bodies attached by a torsional spring, oscillations about the time-varying reference
angle are seen.
.. image:: /_images/Scenarios/scenarioDeployingPanel3.svg
:align: center
.. image:: /_images/Scenarios/scenarioDeployingPanel4.svg
:align: center
The final plot shows the power output. As the first panel deploys, power starts to be generated.
The second panel also generates power, but stops short of full deployment and thus generates less power.
.. image:: /_images/Scenarios/scenarioDeployingPanel5.svg
:align: center
"""
#
# Basilisk Scenario Script and Integrated Test
#
# Purpose: Scenario script and test of the hingedRigidBodyStateEffector with changes
# made to permit deployment.
# Author: Galen Bascom
# Creation Date: Sept. 12, 2022
#
import os
import matplotlib.pyplot as plt
import numpy as np
from Basilisk import __path__
bskPath = __path__[0]
fileName = os.path.basename(os.path.splitext(__file__)[0])
from Basilisk.simulation import spacecraft
from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion,
simIncludeGravBody, unitTestSupport, vizSupport)
from Basilisk.simulation import hingedRigidBodyStateEffector, simpleSolarPanel
from Basilisk.simulation import hingedBodyLinearProfiler, hingedRigidBodyMotor
import math
[docs]
def run(show_plots):
"""
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()
# create the simulation process
dynProcess = scSim.CreateNewProcess(simProcessName)
# create the dynamics task and specify the integration update time
simulationTimeStep = macros.sec2nano(.1)
dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
# create the spacecraft hub
scObject = spacecraft.Spacecraft()
scObject.ModelTag = "bskSat"
scObject.hub.mHub = 750.0
scObject.hub.IHubPntBc_B = [[900.0, 0.0, 0.0], [0.0, 800.0, 0.0], [0.0, 0.0, 600.0]]
# setup Earth Gravity Body
gravFactory = simIncludeGravBody.gravBodyFactory()
gravBodies = gravFactory.createBodies('earth', 'sun')
gravBodies['earth'].isCentralBody = True
mu = gravBodies['earth'].mu
sun = 1
gravFactory.addBodiesTo(scObject)
timeInitString = "2012 MAY 1 00:28:30.0"
spiceObject = gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True)
spiceObject.zeroBase = 'earth'
scSim.AddModelToTask(simTaskName, spiceObject)
# setup the orbit using classical orbit elements
oe = orbitalMotion.ClassicElements()
rLEO = 7000. * 1000 # meters
rGEO = 42000. * 1000 # meters
oe.a = (rLEO + rGEO) / 2.0
oe.e = 1.0 - rLEO / oe.a
oe.i = 0.0 * macros.D2R
oe.Omega = 48.2 * macros.D2R
oe.omega = 347.8 * macros.D2R
oe.f = 85.3 * macros.D2R
rN, vN = orbitalMotion.elem2rv(mu, oe)
oe = orbitalMotion.rv2elem(mu, rN, vN) # this stores consistent initial orbit elements
# To set the spacecraft initial conditions, the following initial position and velocity variables are set:
scObject.hub.r_CN_NInit = rN # m - r_BN_N
scObject.hub.v_CN_NInit = vN # m/s - v_BN_N
# point the body 3 axis towards the sun in the inertial n2 direction
scObject.hub.sigma_BNInit = [[math.tan(-90. / 4. * macros.D2R)], [0.0], [0.0]] # sigma_BN_B
scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B
# configure panels
panel1 = hingedRigidBodyStateEffector.HingedRigidBodyStateEffector()
panel1.ModelTag = "panel1"
panel1.mass = 100.0
panel1.IPntS_S = [[100.0, 0.0, 0.0], [0.0, 50.0, 0.0], [0.0, 0.0, 50.0]]
panel1.d = 1.5
panel1.k = 200.
panel1.c = 20.
panel1.r_HB_B = [[-.5], [0.0], [-1.0]]
panel1.dcm_HB = [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]]
panel1.thetaInit = -np.pi
panel1.thetaDotInit = 0
panel2 = hingedRigidBodyStateEffector.HingedRigidBodyStateEffector()
panel2.ModelTag = "panel2"
panel2.mass = 100.0
panel2.IPntS_S = [[100.0, 0.0, 0.0], [0.0, 50.0, 0.0], [0.0, 0.0, 50.0]]
panel2.d = 1.5
panel2.k = 200.
panel2.c = 20.
panel2.r_HB_B = [[.5], [0.0], [-1.0]]
panel2.dcm_HB = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
panel2.thetaInit = -np.pi
panel2.thetaDotInit = 0
# profilers
profiler1 = hingedBodyLinearProfiler.HingedBodyLinearProfiler()
profiler1.ModelTag = "deploymentProfiler"
profiler1.startTime = macros.sec2nano(5) # [ns] start the deployment
profiler1.endTime = macros.sec2nano(85) # [ns]
profiler1.startTheta = -np.pi # [rad] starting angle in radians
profiler1.endTheta = -np.pi / 2 # [rad]
profiler2 = hingedBodyLinearProfiler.HingedBodyLinearProfiler()
profiler2.ModelTag = "deploymentProfiler2"
profiler2.startTime = macros.sec2nano(100) # [ns] start the deployment
profiler2.endTime = macros.sec2nano(164) # [ns]
profiler2.startTheta = -np.pi # [rad] starting angle in radians
profiler2.endTheta = -np.pi / 1.75 # [rad] ending angle is not all the way deployed
panel1.hingedRigidBodyRefMsg.subscribeTo(profiler1.hingedRigidBodyReferenceOutMsg)
panel2.hingedRigidBodyRefMsg.subscribeTo(profiler2.hingedRigidBodyReferenceOutMsg)
# motors
motor1 = hingedRigidBodyMotor.HingedRigidBodyMotor()
motor1.ModelTag = "hingedRigidBodyMotor"
motor1.K = 20 # proportional gain constant
motor1.P = 10 # derivative gain constant
motor2 = hingedRigidBodyMotor.HingedRigidBodyMotor()
motor2.ModelTag = "hingedRigidBodyMotor2"
motor2.K = 20 # proportional gain constant
motor2.P = 10 # derivative gain constant
motor1.hingedBodyStateSensedInMsg.subscribeTo(panel1.hingedRigidBodyOutMsg)
motor1.hingedBodyStateReferenceInMsg.subscribeTo(profiler1.hingedRigidBodyReferenceOutMsg)
panel1.motorTorqueInMsg.subscribeTo(motor1.motorTorqueOutMsg)
motor2.hingedBodyStateSensedInMsg.subscribeTo(panel2.hingedRigidBodyOutMsg)
motor2.hingedBodyStateReferenceInMsg.subscribeTo(profiler2.hingedRigidBodyReferenceOutMsg)
panel2.motorTorqueInMsg.subscribeTo(motor2.motorTorqueOutMsg)
# add panel to spacecraft hub
scObject.addStateEffector(panel1) # in order to affect dynamics
scObject.addStateEffector(panel2) # in order to affect dynamics
# power
solarPanel1 = simpleSolarPanel.SimpleSolarPanel()
solarPanel1.ModelTag = "pwr1"
solarPanel1.nHat_B = [0, 0, 1]
solarPanel1.panelArea = 2.0 # m^2
solarPanel1.panelEfficiency = 0.9 # 90% efficiency in power generation
solarPanel1.stateInMsg.subscribeTo(panel1.hingedRigidBodyConfigLogOutMsg)
solarPanel1.sunInMsg.subscribeTo(spiceObject.planetStateOutMsgs[sun])
solarPanel2 = simpleSolarPanel.SimpleSolarPanel()
solarPanel2.ModelTag = "pwr2"
solarPanel2.nHat_B = [0, 0, 1]
solarPanel2.panelArea = 2.0 # m^2
solarPanel2.panelEfficiency = 0.9 # 90% efficiency in power generation
solarPanel2.stateInMsg.subscribeTo(panel2.hingedRigidBodyConfigLogOutMsg)
solarPanel2.sunInMsg.subscribeTo(spiceObject.planetStateOutMsgs[sun])
#
# add modules to simulation task list
#
scSim.AddModelToTask(simTaskName, scObject)
scSim.AddModelToTask(simTaskName, panel1)
scSim.AddModelToTask(simTaskName, profiler1)
scSim.AddModelToTask(simTaskName, motor1)
scSim.AddModelToTask(simTaskName, panel2)
scSim.AddModelToTask(simTaskName, profiler2)
scSim.AddModelToTask(simTaskName, motor2)
scSim.AddModelToTask(simTaskName, solarPanel1)
scSim.AddModelToTask(simTaskName, solarPanel2)
# set the simulation time
simulationTime = macros.sec2nano(240)
# Setup data logging before the simulation is initialized
numDataPoints = 1000
samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints)
dataLog = scObject.scStateOutMsg.recorder(samplingTime)
p1Log = panel1.hingedRigidBodyOutMsg.recorder(samplingTime)
prof1Log = profiler1.hingedRigidBodyReferenceOutMsg.recorder(samplingTime)
motor1Log = motor1.motorTorqueOutMsg.recorder(samplingTime)
p2Log = panel2.hingedRigidBodyOutMsg.recorder(samplingTime)
prof2Log = profiler2.hingedRigidBodyReferenceOutMsg.recorder(samplingTime)
motor2Log = motor2.motorTorqueOutMsg.recorder(samplingTime)
pwr1Log = solarPanel1.nodePowerOutMsg.recorder(samplingTime)
pwr2Log = solarPanel2.nodePowerOutMsg.recorder(samplingTime)
scSim.AddModelToTask(simTaskName, dataLog)
scSim.AddModelToTask(simTaskName, p1Log)
scSim.AddModelToTask(simTaskName, prof1Log)
scSim.AddModelToTask(simTaskName, motor1Log)
scSim.AddModelToTask(simTaskName, p2Log)
scSim.AddModelToTask(simTaskName, prof2Log)
scSim.AddModelToTask(simTaskName, motor2Log)
scSim.AddModelToTask(simTaskName, pwr1Log)
scSim.AddModelToTask(simTaskName, pwr2Log)
if vizSupport.vizFound:
viz = vizSupport.enableUnityVisualization(scSim, simTaskName,
[scObject
, [panel1.ModelTag, panel1.hingedRigidBodyConfigLogOutMsg]
, [panel2.ModelTag, panel2.hingedRigidBodyConfigLogOutMsg]
]
# , saveFile=__file__
)
vizSupport.createCustomModel(viz
, simBodiesToModify=[panel1.ModelTag]
, modelPath="CUBE"
, scale=[3, 1, 0.1]
, color=vizSupport.toRGBA255("blue"))
vizSupport.createCustomModel(viz
, simBodiesToModify=[panel2.ModelTag]
, modelPath="CUBE"
, scale=[3, 1, 0.1]
, color=vizSupport.toRGBA255("blue"))
viz.settings.orbitLinesOn = -1
scSim.InitializeSimulation()
scSim.ConfigureStopTime(simulationTime)
scSim.ExecuteSimulation()
# retrieve the logged data
dataSigmaBN = dataLog.sigma_BN
dataOmegaBN_B = dataLog.omega_BN_B
panel1thetaLog = p1Log.theta
panel1thetaDotLog = p1Log.thetaDot
panel2thetaLog = p2Log.theta
panel2thetaDotLog = p2Log.thetaDot
pwrLog1 = pwr1Log.netPower
pwrLog2 = pwr2Log.netPower
np.set_printoptions(precision=16)
figureList = plotOrbits(dataLog.times(), dataSigmaBN, dataOmegaBN_B,
panel1thetaLog, panel1thetaDotLog,
panel2thetaLog, panel2thetaDotLog,
pwrLog1, pwrLog2)
if show_plots:
plt.show()
# close the plots being saved off to avoid over-writing old and new figures
plt.close("all")
return figureList
def plotOrbits(timeAxis, dataSigmaBN, dataOmegaBN,
panel1thetaLog, panel1thetaDotLog,
panel2thetaLog, panel2thetaDotLog,
pwrLog1, pwrLog2):
plt.close("all") # clears out plots from earlier test runs
# sigma B/N
figCounter = 1
plt.figure(figCounter)
fig = plt.gcf()
ax = fig.gca()
ax.ticklabel_format(useOffset=False, style='plain')
timeData = timeAxis * macros.NANO2SEC
for idx in range(3):
plt.plot(timeData, dataSigmaBN[:, idx],
color=unitTestSupport.getLineColor(idx, 3),
label=r'$\sigma_' + str(idx) + '$')
plt.legend(loc='lower right')
plt.xlabel('Time [s]')
plt.ylabel(r'MRP Attitude $\sigma_{B/N}$')
figureList = {}
pltName = fileName + str(figCounter)
figureList[pltName] = plt.figure(figCounter)
# omega B/N
figCounter += 1
plt.figure(figCounter)
fig = plt.gcf()
ax = fig.gca()
ax.ticklabel_format(useOffset=False, style='plain')
for idx in range(3):
plt.plot(timeData, dataOmegaBN[:, idx],
color=unitTestSupport.getLineColor(idx, 3),
label=r'$\omega_' + str(idx) + '$')
plt.legend(loc='lower right')
plt.xlabel('Time [s]')
plt.ylabel(r'Rate $\omega_{B/N}$')
pltName = fileName + str(figCounter)
figureList[pltName] = plt.figure(figCounter)
# rotating panel hinge angle: panel 1
figCounter += 1
plt.figure(figCounter)
ax1 = plt.figure(figCounter).add_subplot(111)
ax1.plot(timeData, panel1thetaLog, color='royalblue')
plt.xlabel('Time [s]')
plt.ylabel('Panel 1 Angle [rad]', color='royalblue')
ax2 = plt.figure(figCounter).add_subplot(111, sharex=ax1, frameon=False)
ax2.plot(timeData, panel1thetaDotLog, color='indianred')
ax2.yaxis.tick_right()
ax2.yaxis.set_label_position("right")
plt.ylabel('Panel 1 Angle Rate [rad/s]', color='indianred')
pltName = fileName + str(figCounter)
figureList[pltName] = plt.figure(figCounter)
# rotating panel hinge angle: panel 2
figCounter += 1
plt.figure(figCounter)
ax1 = plt.figure(figCounter).add_subplot(111)
ax1.plot(timeData, panel2thetaLog, color='royalblue')
plt.xlabel('Time [s]')
plt.ylabel('Panel 2 Angle [rad]', color='royalblue')
ax2 = plt.figure(figCounter).add_subplot(111, sharex=ax1, frameon=False)
ax2.plot(timeData, panel2thetaDotLog, color='indianred')
ax2.yaxis.tick_right()
ax2.yaxis.set_label_position("right")
plt.ylabel('Panel 2 Angle Rate [rad/s]', color='indianred')
pltName = fileName + str(figCounter)
figureList[pltName] = plt.figure(figCounter)
# power: panel 1
figCounter += 1
plt.figure(figCounter)
ax1 = plt.figure(figCounter).add_subplot(111)
ax1.plot(timeData, pwrLog1, color='goldenrod', label="Panel 1")
ax1.plot(timeData, pwrLog2, '--', color='goldenrod', label="Panel 2")
plt.xlabel('Time [s]')
plt.ylabel('Panel Power [W]')
plt.legend(loc='lower right')
pltName = fileName + str(figCounter)
figureList[pltName] = plt.figure(figCounter)
return figureList
if __name__ == "__main__":
run(
True # show_plots
)