Source code for simIncludeRW

#
#  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
import numpy

from Basilisk.utilities import macros
from Basilisk.architecture import messaging

try:
    from collections.abc import OrderedDict
except ImportError:
    from collections import OrderedDict

[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 : initial RW speed in RPM Omega_max : maximum RW speed in RPM rWB_B : 3x1 list of RW center of mass position coordinates RWModel : RW model type such as BalancedWheels, JitterSimple and JitterFullyCoupled useRWfriction : BOOL to turn on RW internal wheel friction useMinTorque : BOOL to clip any torque below a minimum torque value useMaxTorque : BOOL to clip any torque value above a maximum torque value u_max: double with the maximum RW motor torque maxMomentum : maximum RW wheel momentum in Nms. This is a required variable for some wheels. label : string with the unique device name, must be 5 characters or less fCoulomb: double for the Coulomb friction torque model fStatic: double for Static friction torque magnitude betaStatic: double for Stribeck friction coefficient, positive turns Stribeck friction on, negative turns this friction off cViscous: double for Viscous friction coefficient 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 'fCoulomb' in kwargs: varfCoulomb = kwargs['fCoulomb'] if not isinstance(varfCoulomb, float): print('ERROR: fCoulomb must be a FLOAT argument') exit(1) else: varfCoulomb = 0.0 # default value RW.fCoulomb = varfCoulomb if 'fStatic' in kwargs: varfStatic = kwargs['fStatic'] if not isinstance(varfStatic, float): print('ERROR: fStatic must be a FLOAT argument') exit(1) else: varfStatic = 0.0 # default value RW.fStatic = varfStatic 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 if 'cViscous' in kwargs: varcViscous = kwargs['cViscous'] if not isinstance(varcViscous, float): print('ERROR: cViscous must be a FLOAT argument') exit(1) else: varcViscous = 0.0 # default value RW.cViscous = varcViscous # 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 '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 if RW.Omega_max <= 0.0: print('ERROR: RW is being setup with non-positive Omega_max value') exit(1) # spin axis gs inertia [kg*m^2] RW.Js = self.maxMomentum / RW.Omega_max RW.Jt = 0.5 * RW.Js RW.Jg = RW.Jt # 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. :param RW: reaction wheel configuration message :return: """ if self.maxMomentum == 0.0: print("ERROR: simIncludeRW.create() custom RW must have a non-zero maxMomentum specified.") return