# 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.
#
# Unit Test Script
# Module Name: simpleInstrumentController
# Author: Adam Herrmann
# Creation Date: May 19th, 2020
#
import inspect
import os
import pytest
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
bskName = 'Basilisk'
splitPath = path.split(bskName)
# Import all of the modules that we are going to be called in this simulation
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import unitTestSupport
from Basilisk.fswAlgorithms import simpleInstrumentController
from Basilisk.utilities import macros
from Basilisk.architecture import messaging
from Basilisk.architecture import bskLogging
from matplotlib import pyplot as plt
# uncomment this line is this test is to be skipped in the global unit test run, adjust message as needed
# @pytest.mark.skipif(conditionstring)
# uncomment this line if this test has an expected failure, adjust message as needed
# @pytest.mark.xfail(conditionstring)
# provide a unique test method name, starting with test_
tests = [(0, 0, 0, None, None, [1,0,0,1,0]), # rate disabled, device status not written, no controller status
(0, 0.01, 0.1, None, None, [1,0,0,1,0]), # rate disabled, rate noncompliant, device status not written, no controller status
(1, 0.01, 0.001, None, None, [1,0,0,1,0]), # rate enabled, rate compliant, device status not written, no controller status
(1, 0.01, 0.1, None, None, [0, 0, 0, 0, 0]), # rate enabled, rate noncompliant, device status not written, no controller status
(0, 0, 0, 1, 0, [1,0,0,1,0]), # rate disabled, device status of 1, controller status of 0
(0, 0, 0, 0, 1, [0, 0, 0, 0, 0]), # rate disabled, device status of 0, controller status of 1
(0, 0, 0, None, 1, [1,0,0,1,0]) # rate disabled, device status not written, controller status of 1
]
[docs]
@pytest.mark.parametrize('use_rate_limit,rate_limit,omega_mag,deviceStatus,controlStatus,expected_result', tests)
def test_simple_instrument_controller(show_plots, use_rate_limit, rate_limit, omega_mag, deviceStatus, controlStatus, expected_result):
r"""
**Validation Test Description**
Unit test for simpleInstrumentController. The unit test specifically covers:
1. If the controller correctly sends an image command if access and attitude error are within bounds
2. If the controller does not image until the imaged variable is reset to 0
3. If the controller sends an image command again after the imaged variable has been reset to 0
4. If the rate tolerance limit only effects operation when enabled, and effects operation in the expected manner.
5. If the controller sends an image command when deviceStatusInMsg is set to 1 and the instrumentStatus is set to 0
6. If the controller does not send an image command when deviceStatusInMsg is set to 0 and the instrumentStatus is set to 1
7. If the controller does send an image command when deviceStatusInMsg is not written and the instrumentStatus is set to 1
"""
# each test method requires a single assert method to be called
# pass on the testPlotFixture so that the main test function may set the DataStore attributes
[testResults, testMessage] = simpleInstrumentControllerTestFunction(show_plots, use_rate_limit, rate_limit, omega_mag, deviceStatus, controlStatus, expected_result)
assert testResults < 1, testMessage
def simpleInstrumentControllerTestFunction(show_plots, use_rate_limit=1, rate_limit=0.01, omega_mag=0.001, deviceStatus=None, controlStatus=None, expected_result=None):
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty array to store test log messages
unitTaskName = "unitTask"
unitProcessName = "TestProcess"
bskLogging.setDefaultLogLevel(bskLogging.BSK_WARNING)
# Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()
# Create test thread
testProcessRate = macros.sec2nano(1.0)
testProc = unitTestSim.CreateNewProcess(unitProcessName)
testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate))
# Construct algorithm and associated C++ container
module = simpleInstrumentController.simpleInstrumentController()
module.ModelTag = "simpleInstrumentController" # update python name of test module
# Add test module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, module)
# Initialize the test module configuration data
module.attErrTolerance = 0.1 # set the attitude error tolerance
module.rateErrTolerance = rate_limit # set the attitude rate error tolerance
module.useRateTolerance = use_rate_limit # enable attitude rate error tolerance
# Create and write the ground location access message
inputAccessMsgData = messaging.AccessMsgPayload()
inputAccessMsgData.hasAccess = 1
inputAccessMsg = messaging.AccessMsg().write(inputAccessMsgData)
# Create and write the attitude guidance message
inputAttGuidMsgData = messaging.AttGuidMsgPayload()
inputAttGuidMsgData.sigma_BR = [0.01, 0.01, 0.01]
inputAttGuidMsgData.omega_BR_B = [omega_mag, 0.0, 0.0]
inputAttGuidMsg = messaging.AttGuidMsg().write(inputAttGuidMsgData)
# Create and write the device status message
if deviceStatus is not None:
inputDeviceStatusMsgData = messaging.DeviceStatusMsgPayload()
inputDeviceStatusMsgData.deviceStatus = deviceStatus
inputDeviceStatusMsg = messaging.DeviceStatusMsg().write(inputDeviceStatusMsgData)
module.deviceStatusInMsg.subscribeTo(inputDeviceStatusMsg)
# Set the controllerStatus variable
if controlStatus is not None:
module.controllerStatus = controlStatus
# Setup logging on the test module output message so that we get all the writes to it
dataLog = module.deviceCmdOutMsg.recorder()
unitTestSim.AddModelToTask(unitTaskName, dataLog)
# connect the message interfaces
module.locationAccessInMsg.subscribeTo(inputAccessMsg)
module.attGuidInMsg.subscribeTo(inputAttGuidMsg)
# Need to call the self-init and cross-init methods
unitTestSim.InitializeSimulation()
# Set the simulation time.
# NOTE: the total simulation time may be longer than this value. The
# simulation is stopped at the next logging event on or after the
# simulation end time.
unitTestSim.ConfigureStopTime(macros.sec2nano(1.0)) # seconds to stop simulation
# Begin the simulation time run set above
unitTestSim.ExecuteSimulation()
# run the module again for an additional second
unitTestSim.ConfigureStopTime(macros.sec2nano(2.0)) # seconds to stop simulation
unitTestSim.ExecuteSimulation()
# Now change the imaged variable back to 0 and run again for another image
module.imaged = 0
unitTestSim.ConfigureStopTime(macros.sec2nano(4.0)) # seconds to stop simulation
unitTestSim.ExecuteSimulation()
if not unitTestSupport.isArrayEqual(dataLog.deviceCmd, expected_result, len(expected_result), 1e-12):
testFailCount += 1
testMessages.append("FAILED: " + module.ModelTag + " Module failed dataVector" + " unit test at t=" + str(dataLog.times()[0]*macros.NANO2SEC) + "sec\n")
# Plots
plt.close("all") # close all prior figures so we start with a clean slate
plt.figure(1)
plt.plot(dataLog.times() * macros.NANO2SEC, dataLog.deviceCmd)
plt.xlabel('Time [s]')
plt.ylabel('Device Status')
plt.suptitle('Device Status Over Time')
if show_plots:
plt.show()
# each test method requires a single assert method to be called
# this check below just makes sure no sub-test failures were found
return [testFailCount, ''.join(testMessages)]
#
# This statement below ensures that the unitTestScript can be run as a
# stand-along python script
#
if __name__ == "__main__":
simpleInstrumentControllerTestFunction(
True # show_plots
)