#
# 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.
#
#
# Simulation Setup Utilities for RW
#
import sys
from collections import OrderedDict
import numpy
from Basilisk.architecture import messaging
from Basilisk.utilities import macros
[docs]
class rwFactory(object):
"""
Reaction Wheel Factory Class
"""
def __init__(self):
self.rwList = OrderedDict()
self.maxMomentum = 0.0
[docs]
def create(self, rwType, gsHat_B, **kwargs):
"""
This function is called to setup a RW device in python, and adds it to the RW factory
list rwList{}. The function returns a copy of the device that can be changed if needed.
The first 2 arguments are required, the remaining arguments are optional with:
Parameters
----------
rwType : string
RW manufacturing name.
gsHat_B : list
Spin axis unit vector gsHat in B-frame components
kwargs:
Omega: float
initial RW speed in RPM
Omega_max: float
maximum RW speed in RPM
rWB_B: list
3x1 list of RW center of mass position coordinates
RWModel: integer
RW model type such as BalancedWheels, JitterSimple and JitterFullyCoupled
useRWfriction: BOOL
conditional to turn on RW internal wheel friction
useMinTorque: BOOL
conditional to clip any torque below a minimum torque value
useMaxTorque: BOOL
conditional to clip any torque value above a maximum torque value
u_max: float
the maximum RW motor torque
maxMomentum: float
maximum RW wheel momentum in Nms. This is a required variable for some wheels.
P_max: float
the maximum allowed wheel power for changing wheel speed (does not include a base power requirement)
label: string
with the unique device name, must be 5 characters or less
fCoulomb: float
Coulomb friction torque model coefficient
fStatic: float
Static friction torque magnitude
betaStatic: float
Stribeck friction coefficient, positive turns Stribeck friction on, negative turns this friction off
cViscous: float
Viscous friction coefficient
Js: float
RW inertia about spin axis
Returns
-------
RWConfigSimMsg : message structure
A handle to the RW configuration message
"""
# create the blank RW object
RW = messaging.RWConfigMsgPayload()
# process optional input arguments
if 'RWModel' in kwargs:
varRWModel = kwargs['RWModel']
if not isinstance(varRWModel, int):
print('ERROR: RWModel must be a INT argument')
exit(1)
else:
varRWModel = messaging.BalancedWheels # default value
if 'useRWfriction' in kwargs:
varUseRWfriction = kwargs['useRWfriction']
if not isinstance(varUseRWfriction, bool):
print('ERROR: useRWfriction must be a BOOL argument')
exit(1)
else:
varUseRWfriction = False # default value
if 'useMinTorque' in kwargs:
varUseMinTorque = kwargs['useMinTorque']
if not isinstance(varUseMinTorque, bool):
print('ERROR: useMinTorque must be a BOOL argument')
exit(1)
else:
varUseMinTorque = False # default value
if 'useMaxTorque' in kwargs:
varUseMaxTorque = kwargs['useMaxTorque']
if not isinstance(varUseMaxTorque, bool):
print('ERROR: useMaxTorque must be a BOOL argument')
exit(1)
else:
varUseMaxTorque = True # default value
if 'maxMomentum' in kwargs:
varMaxMomentum = kwargs['maxMomentum']
if not isinstance(varMaxMomentum, float):
print('ERROR: maxMomentum must be a FLOAT argument')
exit(1)
else:
varMaxMomentum = 0.0 # default value
self.maxMomentum = varMaxMomentum
if 'P_max' in kwargs:
varMaxPower = kwargs['P_max']
if not isinstance(varMaxPower, float):
print('ERROR: P_max must be a FLOAT argument')
exit(1)
else:
varMaxPower = -1.0 # default value turns off max power limit
RW.P_max = varMaxPower
if 'betaStatic' in kwargs:
varbetaStatic = kwargs['betaStatic']
if not isinstance(varbetaStatic, float):
print('ERROR: betaStatic must be a FLOAT argument')
exit(1)
if varbetaStatic == 0:
print('ERROR: betaStatic cannot be set to zero. Positive turns it on, negative turns it off')
exit(1)
else:
varbetaStatic = -1.0 # default value turns off Stribeck friction model
RW.betaStatic = varbetaStatic
# set device label name
if 'label' in kwargs:
varLabel = kwargs['label']
if not isinstance(varLabel, str):
print('ERROR: label must be a string')
exit(1)
if len(varLabel) > 5:
print('ERROR: RW label string is longer than 5 characters')
exit(1)
else:
varLabel = 'RW' + str(len(self.rwList)+1) # default device labeling
RW.label = varLabel
# populate the RW object with the type specific parameters
try:
eval('self.' + rwType + '(RW)')
except:
print('ERROR: RW type ' + rwType + ' is not implemented')
exit(1)
if 'fCoulomb' in kwargs:
RW.fCoulomb = kwargs['fCoulomb']
if not isinstance(RW.fCoulomb, float):
print('ERROR: fCoulomb must be a FLOAT argument')
exit(1)
if 'fStatic' in kwargs:
RW.fStatic = kwargs['fStatic']
if not isinstance(RW.fStatic, float):
print('ERROR: fStatic must be a FLOAT argument')
exit(1)
if 'cViscous' in kwargs:
RW.cViscous = kwargs['cViscous']
if not isinstance(RW.cViscous, float):
print('ERROR: cViscous must be a FLOAT argument')
exit(1)
if 'u_min' in kwargs:
varu_min = kwargs['u_min']
if not isinstance(varu_min, float):
print('ERROR: u_min must be a FLOAT argument')
exit(1)
RW.u_min = varu_min
if RW.u_min <= 0.0 and varUseMinTorque:
print('ERROR: RW is being setup with non-positive u_min value with varUseMinTorque set to True')
exit(1)
if 'u_max' in kwargs:
varu_max = kwargs['u_max']
if not isinstance(varu_max, float):
print('ERROR: u_max must be a FLOAT argument')
exit(1)
RW.u_max = varu_max
if RW.u_max <= 0.0 and varUseMaxTorque:
print('ERROR: RW is being setup with non-positive u_max value with varUseMaxTorque set to True')
exit(1)
# set initial RW states
if 'Omega_max' in kwargs:
varOmega_max = kwargs['Omega_max']
if not isinstance(varOmega_max, float):
print('ERROR: Omega_max must be a FLOAT argument')
exit(1)
RW.Omega_max = varOmega_max * macros.RPM
# set RW spin axis inertia
RW.Js = -1.0
if 'Js' in kwargs:
varJs = kwargs['Js']
if not isinstance(varJs, float):
print('ERROR: Js must be a FLOAT argument')
exit(1)
if varJs > 0.0:
RW.Js = varJs
RW.Jt = 0.5 * RW.Js
RW.Jg = RW.Jt
else:
print('ERROR: Js must be a positive value')
exit(1)
if RW.Omega_max > 0.0 and self.maxMomentum > 0.0:
if RW.Js <= 0.0: # no inertia specified
# spin axis gs inertia [kg*m^2]
RW.Js = self.maxMomentum / RW.Omega_max
RW.Jt = 0.5 * RW.Js
RW.Jg = RW.Jt
else:
print('ERROR: rwFactory tried to set Js both directly and through maxMomentum and Omega_max')
exit(1)
if RW.Js < 0.0:
print('ERROR: RW Js value not specified direct, nor indirectly using maxMomentum and Omega_max')
# set RW axes
self.setGsHat(RW, gsHat_B)
# set RW position vector
if 'rWB_B' in kwargs:
varrWB_B = kwargs['rWB_B']
if not isinstance(varrWB_B, list):
print('ERROR: rWB_B must be a 3x1 list argument')
exit(1)
if not len(varrWB_B) == 3:
print('ERROR: rWB_B has dimension ' + str(len(varrWB_B)) + ', must be a 3x1 list argument')
exit(1)
else:
varrWB_B = [0., 0., 0.] # default value
RW.rWB_B = varrWB_B
# set initial RW states
if 'Omega' in kwargs:
varOmega = kwargs['Omega']
if not isinstance(varOmega, (float)):
print('ERROR: Omega must be a FLOAT argument')
exit(1)
else:
varOmega = 0.0 # default value
RW.Omega = varOmega * macros.RPM
RW.theta = 0.0 * macros.D2R
# enforce some RW options
RW.RWModel = varRWModel
if not varUseRWfriction:
RW.fCoulomb = 0.0
if not varUseMaxTorque:
RW.u_max = -1 # a negative value turns off RW torque saturation
if not varUseMinTorque:
RW.u_min = 0.0
# add RW to the list of RW devices
RW.this.disown()
self.rwList[varLabel] = RW
return RW
[docs]
def setGsHat(self, RW, gsHat_B):
"""
Function to set the gsHat_B RW spin axis vector. This function
automatically computes to companion transfer axes to complete a
wheel reference frame.
:param RW:
:param gsHat_B:
"""
# set RW spin axis gsHat
norm = numpy.linalg.norm(gsHat_B)
if norm > 1e-10:
gsHat_B = gsHat_B / norm
else:
print('Error: RW gsHat input must be non-zero 3x1 vector')
exit(1)
RW.gsHat_B = [[gsHat_B[0]], [gsHat_B[1]], [gsHat_B[2]]]
# set RW t and g unit axes
w2Hat0_B = numpy.cross(gsHat_B, [1, 0, 0])
norm = numpy.linalg.norm(w2Hat0_B)
if norm < 0.01:
w2Hat0_B = numpy.cross(gsHat_B, [0, 1, 0])
norm = numpy.linalg.norm(w2Hat0_B)
w2Hat0_B = w2Hat0_B / norm
w3Hat0_B = numpy.cross(gsHat_B, w2Hat0_B)
RW.w2Hat0_B = [[w2Hat0_B[0]], [w2Hat0_B[1]], [w2Hat0_B[2]]]
RW.w3Hat0_B = [[w3Hat0_B[0]], [w3Hat0_B[1]], [w3Hat0_B[2]]]
return
[docs]
def addToSpacecraft(self, modelTag, rwStateEffector, sc):
"""
This function should be called after all RW devices are created with createRW()
It creates the C-class container for the array of RW devices, and attaches
this container to the spacecraft object
Parameters
----------
:param modelTag: string with the model tag
:param rwStateEffector:
:param sc: spacecraft object
"""
rwStateEffector.ModelTag = modelTag
for key, rw in list(self.rwList.items()):
rwStateEffector.addReactionWheel(rw)
sc.addStateEffector(rwStateEffector)
return
[docs]
def getNumOfDevices(self):
"""
Returns the number of RW devices setup.
Returns
-------
:return: int
"""
return len(self.rwList)
[docs]
def getConfigMessage(self):
"""
Returns a FSW reaction wheel configuration message based on the current setup.
:return: RWArrayConfigMsg
"""
GsMatrix_B = []
JsList = []
uMaxList = []
for rw in self.rwList.values():
flatGsHat = [element for sublist in rw.gsHat_B for element in sublist]
GsMatrix_B.extend(flatGsHat)
JsList.extend([rw.Js])
uMaxList.extend([rw.u_max])
rwConfigParams = messaging.RWArrayConfigMsgPayload()
rwConfigParams.GsMatrix_B = GsMatrix_B
rwConfigParams.JsList = JsList
rwConfigParams.uMax = uMaxList
rwConfigParams.numRW = len(self.rwList)
rwConfigMsg = messaging.RWArrayConfigMsg().write(rwConfigParams)
rwConfigMsg.this.disown()
return rwConfigMsg
#
# Honeywell HR16 (100Nm, 75Nm, 50Nm)
#
# RW Information Source:
# http://www51.honeywell.com/aero/common/documents/Constellation_Series_Reaction_Wheels.pdf
#
# There are 3 momentum capacity options for this RW type. The maximum momentum
# capacity must be set prior to creating the HR16 RW type using
# maxMomentum = 100, 75 or 50
#
def Honeywell_HR16(self, RW):
# maximum allowable wheel speed
RW.Omega_max = 6000.0*macros.RPM
# maximum RW torque [Nm]
RW.u_max = 0.200
# minimum RW torque [Nm]
RW.u_min = 0.00001
# static friction torque [Nm]
RW.fCoulomb = 0.0005
# RW rotor mass [kg]
# Note: the rotor mass here is set equal to the RW mass of the above spec sheet.
# static RW imbalance [kg*m]
# dynamic RW imbalance [kg*m^2]
large = 100
medium = 75
small = 50
if self.maxMomentum == large:
RW.mass = 12.0
RW.U_s = 4.8E-6
RW.U_d = 15.4E-7
elif self.maxMomentum == medium:
RW.mass = 10.4
RW.U_s = 3.8E-6
RW.U_d = 11.5E-7
elif self.maxMomentum == small:
RW.mass = 9.0
RW.U_s = 2.8E-6
RW.U_d = 7.7E-7
else:
if self.maxMomentum > 0:
print('ERROR: ' + sys._getframe().f_code.co_name + '() does not have a correct wheel momentum of '\
+str(large)+', '+str(medium)+' or '+str(small)+' Nm. Provided ' + str(self.maxMomentum) + ' Nm')
else:
print('ERROR: ' + sys._getframe().f_code.co_name \
+ '() maxMomentum option must be set prior to calling createRW()')
exit(1)
return
#
# Honeywell HR14 (25Nm, 50Nm, 75Nm)
#
# RW Information Source:
# http://www51.honeywell.com/aero/common/documents/Constellation_Series_Reaction_Wheels.pdf
#
# There are 3 momentum capacity options for this RW type. The maximum momentum
# capacity must be set prior to creating the HR14 RW type using
# options.maxMomentum = 75, 50 or 25
#
def Honeywell_HR14(self, RW):
# maximum allowable wheel speed
RW.Omega_max = 6000.0*macros.RPM
# maximum RW torque [Nm]
RW.u_max = 0.200
# minimum RW torque [Nm]
RW.u_min = 0.00001
# static friction torque [Nm]
RW.fCoulomb = 0.0005
# RW rotor mass [kg]
# Note: the rotor mass here is set equal to the RW mass of the above spec sheet.
# static RW imbalance [kg*m]
# dynamic RW imbalance [kg*m^2]
large = 75
medium = 50
small = 25
if self.maxMomentum == large:
RW.mass = 10.6
RW.U_s = 4.8E-6
RW.U_d = 13.7E-7
elif self.maxMomentum == medium:
RW.mass = 8.5
RW.U_s = 3.5E-6
RW.U_d = 9.1E-7
elif self.maxMomentum == small:
RW.mass = 7.5
RW.U_s = 2.2E-6
RW.U_d = 4.6E-7
else:
if self.maxMomentum > 0:
print('ERROR: ' + sys._getframe().f_code.co_name + '() does not have a correct wheel momentum of '\
+str(large)+', '+str(medium)+' or '+str(small)+' Nm. Provided ' + str(self.maxMomentum) + ' Nm')
else:
print('ERROR: ' + sys._getframe().f_code.co_name \
+ '() maxMomentum option must be set prior to calling createRW()')
exit(1)
return
#
# Honeywell HR12 (12Nm, 25Nm, 50Nm)
#
# RW Information Source:
# http://www51.honeywell.com/aero/common/documents/Constellation_Series_Reaction_Wheels.pdf
#
# There are 3 momentum capacity self for this RW type. The maximum momentum
# capacity must be set prior to creating the HR12 RW type using
# maxMomentum = 12, 25 or 50
#
def Honeywell_HR12(self, RW):
# maximum allowable wheel speed
RW.Omega_max = 6000.0*macros.RPM
# maximum RW torque [Nm]
RW.u_max = 0.200
# minimum RW torque [Nm]
RW.u_min = 0.00001
# static friction torque [Nm]
RW.fCoulomb = 0.0005
# RW rotor mass [kg]
# Note: the rotor mass here is set equal to the RW mass of the above spec sheet.
# static RW imbalance [kg*m]
# dynamic RW imbalance [kg*m^2]
large = 50
medium = 25
small = 12
if self.maxMomentum == large:
RW.mass = 9.5
RW.U_s = 4.4E-6
RW.U_d = 9.1E-7
elif self.maxMomentum == medium:
RW.mass = 7.0
RW.U_s = 2.4E-6
RW.U_d = 4.6E-7
elif self.maxMomentum == small:
RW.mass = 6.0
RW.U_s = 1.5E-6
RW.U_d = 2.2E-7
else:
if self.maxMomentum > 0:
print('ERROR: ' + sys._getframe().f_code.co_name + '() does not have a correct wheel momentum of '\
+str(large)+', '+str(medium)+' or '+str(small)+' Nm. Provided ' + str(self.maxMomentum) + ' Nm')
else:
print('ERROR: ' + sys._getframe().f_code.co_name \
+ '() maxMomentum option must be set prior to calling createRW()')
exit(1)
return
[docs]
def BCT_RWP015(self, RW):
"""
BCT RWP015
RW Information Source:
https://storage.googleapis.com/blue-canyon-tech-news/1/2019/10/BCT_DataSheet_Components_ReactionWheels_F2.pdf
Not complete; fields not listed are estimates.
:param RW: reaction wheel configuration message
:return:
"""
# maximum allowable wheel speed
RW.Omega_max = 6000.0*macros.RPM
# maximum RW torque [Nm]
RW.u_max = 0.004
# minimum RW torque [Nm]
RW.u_min = 0.00001
# static friction torque [Nm]
RW.fCoulomb = 0.00005
# RW rotor mass [kg]
# Note: the rotor mass here is set equal to the RW mass of the above spec sheet.
# static RW imbalance [kg*m]
# dynamic RW imbalance [kg*m^2]
if self.maxMomentum > 0.0:
print("WARNING: BCT_RWP015 has a fixed maxMomentum value. Custom value being replaced.")
self.maxMomentum = 0.015 # Nms
RW.mass = 0.130
RW.U_s = 1E-7 # Guestimate
RW.U_d = 1E-8 # Guestimate
return
[docs]
def custom(self, RW):
"""
Creates an empty reaction wheel configuration message. This assumes the user provided the
RW maximum speed and maximum angular momentum information so that Js can be computed,
or the user provides the Js inertia value directly.
:param RW: reaction wheel configuration message
:return:
"""
return