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
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 '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_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 * macros.RPM 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