# 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.
#
# Coarse Sun Sensor Unit Test
#
# Purpose: Test the proper function of the coarse sun sensor (css) module.
# For basic functionality, results are compared to simple truth values calculated using np.cos().
# For noise testing, noiseless truth values are subtracted from the output and the standard deviation is compared
# to the input standard deviation.
# For css constellation set up, two identical constellations are set up with different methods and compared to
# each other
# Creation Date: May. 31, 2017
#
import numpy as np
import pytest
from Basilisk.architecture import messaging
from Basilisk.simulation import coarseSunSensor
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import orbitalMotion as om
# The following 'parametrize' function decorator provides the parameters and expected results for each
# of the multiple test runs for this test.
[docs]
@pytest.mark.parametrize(
"cssFault",
[
"CSSFAULT_OFF",
"CSSFAULT_STUCK_CURRENT",
"CSSFAULT_STUCK_MAX",
"CSSFAULT_STUCK_RAND",
"CSSFAULT_RAND",
],
)
# provide a unique test method name, starting with test_
def test_coarseSunSensor(cssFault):
"""Verify that each coarse sun sensor fault has the documented behavior."""
outputs = run(cssFault)
nominalOutput = outputs[0]
faultOutputs = outputs[1:]
if cssFault == "CSSFAULT_OFF":
np.testing.assert_array_equal(faultOutputs, np.zeros(3))
elif cssFault == "CSSFAULT_STUCK_CURRENT":
np.testing.assert_allclose(faultOutputs, nominalOutput)
elif cssFault == "CSSFAULT_STUCK_MAX":
expectedOutput = 2.0 # [-] unit signal multiplied by the configured scale factor
np.testing.assert_allclose(faultOutputs, expectedOutput)
elif cssFault == "CSSFAULT_STUCK_RAND":
np.testing.assert_allclose(faultOutputs, faultOutputs[0])
assert not np.isclose(faultOutputs[0], nominalOutput)
elif cssFault == "CSSFAULT_RAND":
assert np.ptp(faultOutputs) > np.finfo(float).eps
faultBound = 4.0 # [-] Gauss-Markov fault bound multiplied by the configured scale factor
assert np.all(np.abs(faultOutputs) <= faultBound)
def run(cssFault):
testTaskName = "unitTestTask"
testProcessName = "unitTestProcess"
testTaskPeriod = 0.1 # [s]
testTaskRate = macros.sec2nano(testTaskPeriod)
# Create a simulation container
unitTestSim = SimulationBaseClass.SimBaseClass()
# unitTestSim.RNGSeed = 10
# Ensure simulation is empty
testProc = unitTestSim.CreateNewProcess(testProcessName)
testProc.addTask(unitTestSim.CreateNewTask(testTaskName, testTaskRate))
# Input Message Setup
# Creates inputs from sun, spacecraft, and eclipse so that those modules don't have to be included
# Create dummy sun message
sunPositionMsg = messaging.SpicePlanetStateMsgPayload()
sunPositionMsg.PositionVector = [om.AU * 1000.0, 0.0, 0.0]
sunMsg = messaging.SpicePlanetStateMsg().write(sunPositionMsg)
# Create dummy spacecraft message
satelliteStateMsg = messaging.SCStatesMsgPayload()
satelliteStateMsg.r_BN_N = [0.0, 0.0, 0.0]
angle = np.pi / 16 # [rad]
satelliteStateMsg.sigma_BN = [0., 0., angle]
scMsg = messaging.SCStatesMsg().write(satelliteStateMsg)
# Calculate sun distance factor
CSS = coarseSunSensor.CoarseSunSensor()
CSS.fov = 80.0 * macros.D2R # [rad] half-angle field of view value
CSS.scaleFactor = 2.0 # [-]
CSS.nHat_B = np.array([1., 0., 0.])
CSS.sunInMsg.subscribeTo(sunMsg)
CSS.stateInMsg.subscribeTo(scMsg)
CSS.ModelTag = "CSS"
CSS.RNGSeed = 123
unitTestSim.AddModelToTask(testTaskName, CSS)
# log single CSS
cssRecoder = CSS.cssDataOutMsg.recorder()
unitTestSim.AddModelToTask(testTaskName, cssRecoder)
cssFaultValue = getattr(coarseSunSensor, cssFault)
unitTestSim.InitializeSimulation()
# Execute the simulation for one time step
unitTestSim.TotalSim.SingleStepProcesses()
CSS.faultState = cssFaultValue
for i in range(3):
unitTestSim.TotalSim.SingleStepProcesses()
return np.asarray(cssRecoder.OutputData)
if __name__ == "__main__":
run("CSSFAULT_STUCK_MAX")