simple_nonlinear.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 #  Copyright (c) 2011, UC Regents
00004 #  All rights reserved.
00005 #
00006 #  Redistribution and use in source and binary forms, with or without
00007 #  modification, are permitted provided that the following conditions
00008 #  are met:
00009 #
00010 #   * Redistributions of source code must retain the above copyright
00011 #     notice, this list of conditions and the following disclaimer.
00012 #   * Redistributions in binary form must reproduce the above
00013 #     copyright notice, this list of conditions and the following
00014 #     disclaimer in the documentation and/or other materials provided
00015 #     with the distribution.
00016 #   * Neither the name of the University of California nor the names of its
00017 #     contributors may be used to endorse or promote products derived
00018 #     from this software without specific prior written permission.
00019 #
00020 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 #  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 #  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 #  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 #  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 #  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 #  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 #  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 #  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 #  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 #  POSSIBILITY OF SUCH DAMAGE.
00032 
00033 import roslib
00034 roslib.load_manifest('simulated_quadrotor')
00035 import rospy
00036 
00037 from math import sin, cos, radians, degrees
00038 import numpy as np
00039 
00040 import tf.transformations as tft
00041 
00042 from starmac_roslib.pid import PidController
00043 
00044 from model_base import ModelBase
00045 
00046 from simulated_quadrotor.msg import SimDebug
00047 
00048 
00049 GRAVITY = 9.81 # m/s/s
00050 MASS = 1.48 # kg
00051 FUDGE = 1500
00052 THRUST_SCALING = MASS*GRAVITY/FUDGE
00053 LINEAR_DRAG = 1.0 # N/(m/s)
00054 QUADRATIC_DRAG = 0.3 # N/(m/(s^2))
00055 EPS = 0.0001
00056 IMU_HEIGHT = 0.17 # how far off the ground the IMU frame is when the vehicle is landed
00057 
00058 # Identified closed-loop attitude dynamics linear model:
00059 n0 = 40.0
00060 d1 = 8.0
00061 d0 = 80.0
00062 
00063 # Angular velocity filter parameters
00064 QUAT_FILT_A = 0.2
00065 QUAT_FILT_B = 0.8
00066 
00067 def clip(x, lb, ub):
00068     if x > ub:
00069         return ub, True
00070     elif x < lb:
00071         return lb, True
00072     else:
00073         return x, False
00074     
00075 def limitSlewRate(x, x_prev, dt, rate_limit):
00076     limit = rate_limit * dt;
00077     return max(x_prev - limit, min(x_prev + limit, x));
00078   
00079 
00080 class SimpleNonlinearModel(ModelBase):
00081     """
00082     Simple nonlinear model of arbitrary quadrotor type vehicle.
00083     13 states:
00084     index    quantity                    units
00085     0        x position                    m
00086     1        y position                    m
00087     2        z position                    m
00088     
00089     3        x velocity                    m/s
00090     4        y velocity                    m/s
00091     5        z velocity                    m/s
00092     
00093     6        orientation quaternion (w)   
00094     7        orientation quaternion (x)   
00095     8        orientation quaternion (y)   
00096     9        orientation quaternion (z)
00097        
00098     10       angular velocity (x)          rad/s
00099     11       angular velocity (y)          rad/s
00100     12       angular velocity (z)          rad/s
00101     
00102     4 inputs:
00103     index    quantity  units
00104     0        yaw       deg
00105     1        pitch     deg
00106     2        roll      deg
00107     3        altitude  m
00108     4        enable    (bool, True if motors are on)
00109     
00110     NOTE: transformations.py (tf.transformations) represents quaternions 
00111           as (x, y, z, w) tuples!
00112     """
00113     def __init__(self):
00114         self.x = np.zeros(13)
00115         self.x[6] = 1.0 # identity quaternion
00116         #self.u = np.zeros(5)
00117         self.q_dot_prev = np.zeros(4)
00118         # Grab parameters:
00119         self.alt_KP = rospy.get_param("~alt_KP", 600.0)
00120         self.alt_KI = rospy.get_param("~alt_KI", 400.0)
00121         self.alt_KD = rospy.get_param("~alt_KD", 600.0)
00122         #self.alt_KDD = rospy.get_param("~alt_KDD",50.0)
00123         self.alt_Ilimit = rospy.get_param("~alt_Ilimit", 400.0)
00124         #self.yaw_KP = rospy.get_param("~yaw_KP", 4.0)
00125         #self.yaw_KI = rospy.get_param("~yaw_KI", 0.0)
00126         #self.yaw_KD = rospy.get_param("~yaw_KD", 0.0)
00127         #self.yaw_Ilimit = rospy.get_param("~yaw_Ilimit", 50.0)
00128         self.nominal_thrust = rospy.get_param("~nominal_thrust", 1500)
00129         self.thrust_mult = rospy.get_param("~thrust_mult", 1.0)
00130         self.max_thrust = rospy.get_param("~max_thrust", 2100)
00131         temp = rospy.get_param("~periodic_accel_disturbance/vector", None)
00132         if temp is None:
00133             self.periodic_accel_disturbance_vector = np.zeros(3)
00134         else:
00135             self.periodic_accel_disturbance_vector = np.array(temp)
00136         self.periodic_accel_disturbance_period = rospy.get_param("~periodic_accel_disturbance/period", 10.0)
00137         self.periodic_accel_disturbance_duration = rospy.get_param("~periodic_accel_disturbance/duration", 1.0)
00138         self.pitch_bias = rospy.get_param("~pitch_bias", 0.0) # degrees
00139         self.roll_bias = rospy.get_param("~roll_bias", 0.0) # degrees
00140         self.roll_slew_rate_limit = rospy.get_param("~roll_slew_rate_limit", 10.0) # deg/s
00141         self.pitch_slew_rate_limit = rospy.get_param("~pitch_slew_rate_limit", 10.0) # deg/s
00142         temp = rospy.get_param("~gaussian_accel_noise_covariance", None) #[0.0]*9)
00143         if temp is not None:
00144             self.gaussian_accel_noise_covariance = np.array(temp).reshape(3,3)
00145         else:
00146             self.gaussian_accel_noise_covariance = None
00147         self.prev_orientation = (0.0, 0.0, 0.0, 1.0)
00148         self.prev_roll_cmd = 0.0
00149         self.prev_pitch_cmd = 0.0
00150         
00151         self.alt_pid = PidController(self.alt_KP, self.alt_KI, self.alt_KD, self.alt_Ilimit)
00152         self.t = 0
00153         
00154         self.debug_publisher = rospy.Publisher('~debug', SimDebug)
00155         
00156     def set_input(self, motors_on, roll_cmd, pitch_cmd, direct_yaw_rate_commands, 
00157                   yaw_cmd, yaw_rate_cmd, direct_thrust_commands, alt_cmd, thrust_cmd):
00158         self.motors_on = motors_on # bool
00159         self.roll_cmd = roll_cmd # deg
00160         self.pitch_cmd = pitch_cmd # deg
00161         self.direct_yaw_rate_commands = direct_yaw_rate_commands # bool
00162         self.yaw_cmd = yaw_cmd # deg
00163         self.yaw_rate_cmd = yaw_rate_cmd # deg/s
00164         self.direct_thrust_commands = direct_thrust_commands # bool
00165         self.alt_cmd = alt_cmd # m
00166         self.thrust_cmd = thrust_cmd # N
00167         
00168         
00169     def update(self, dt):
00170         # The following model is completely arbitrary and should not be taken to be representative of
00171         # real vehicle performance!
00172         # But, it should be good enough to test out control modes etc.
00173         self.t += dt
00174         #yaw_cmd, pitch_cmd, roll_cmd, alt_cmd, motor_enable = self.u
00175         cur_yaw, cur_pitch, cur_roll = tft.euler_from_quaternion(self.get_orientation(), 'rzyx')
00176         prev_yaw, prev_pitch, prev_roll = tft.euler_from_quaternion(self.prev_orientation, 'rzyx')
00177         # Feedback control: attitude angles track inputs exactly; altitude is input to
00178         # PID controller for thrust
00179         
00180         sim_debug_msg = SimDebug()
00181         sim_debug_msg.header.stamp = rospy.Time.now()
00182         
00183         if self.motors_on:
00184             if self.direct_thrust_commands:
00185                 thrust = self.thrust_mult*self.thrust_cmd/THRUST_SCALING
00186             else:
00187                 thrust = self.thrust_mult*(self.nominal_thrust + \
00188                   self.alt_pid.update(-self.get_position()[2] - self.alt_cmd, dt))
00189             thrust = min(self.max_thrust, thrust)
00190             ang_vel_inertial = self.x[10:13]
00191             R_inertial_body = tft.quaternion_matrix(self.get_orientation())[:3,:3]
00192             ang_vel_body = np.dot(R_inertial_body.T, ang_vel_inertial)
00193             # accelerations due to pitch/roll in body frame:
00194             A = np.array([[-d1, 1.0],[-d0, 0.0]])
00195             B = np.array([0, n0])
00196             # Roll:
00197             roll_cmd_slew_limited =  limitSlewRate(self.roll_cmd, self.prev_roll_cmd, dt, self.roll_slew_rate_limit)         
00198             self.prev_roll_cmd = roll_cmd_slew_limited
00199             roll_state_cur = np.array([cur_roll, ang_vel_body[0]])
00200             xdot = np.dot(A,roll_state_cur) + B*radians(roll_cmd_slew_limited)
00201             roll_state_new = roll_state_cur + xdot*dt #+ radians(self.roll_bias)  
00202             # Pitch:
00203             pitch_cmd_slew_limited =  limitSlewRate(self.pitch_cmd, self.prev_pitch_cmd, dt, self.pitch_slew_rate_limit)         
00204             self.prev_pitch_cmd = pitch_cmd_slew_limited
00205             pitch_state_cur = np.array([cur_pitch, ang_vel_body[1]])
00206             xdot = np.dot(A,pitch_state_cur) + B*radians(pitch_cmd_slew_limited)
00207             pitch_state_new = pitch_state_cur + xdot*dt #+ radians(self.pitch_bias)
00208                  
00209             pitch_clipped, pwas_clipped = clip(degrees(pitch_state_new[0]), -50, 50)
00210             roll_clipped, rwas_clipped = clip(degrees(roll_state_new[0]), -50, 50)
00211             # Yaw (no dynamics here for now -- tracks perfectly)
00212             yaw_cmd = self.yaw_cmd
00213         else:
00214             thrust = 0.0
00215             yaw_cmd = degrees(cur_yaw)
00216             pitch_clipped = degrees(cur_pitch)
00217             roll_clipped = degrees(cur_roll)
00218             roll_state_new = np.array([radians(roll_clipped), 0])
00219             pitch_state_new = np.array([radians(pitch_clipped), 0])
00220             pwas_clipped = rwas_clipped = False
00221             
00222         sim_debug_msg.thrust_in_counts = thrust
00223         
00224         # Propagate dynamics
00225         # Velocity
00226         accel_thrust = thrust*THRUST_SCALING/MASS
00227         #rospy.loginfo("accel_thrust = " + str(accel_thrust))
00228         if pwas_clipped or rwas_clipped:
00229             rospy.logwarn('Pitch and/or roll clipped! Pre-clip (commanded) values %f %f' % (self.pitch_cmd, self.roll_cmd))
00230         accel_body = np.array([-sin(radians(pitch_clipped))*accel_thrust, 
00231                               sin(radians(roll_clipped))*accel_thrust, 
00232                               0.0]) # vertical accel will be added in inertial frame
00233         # rotate body frame accelerations into inertial frame, and add drag and vertical forces:
00234         Ryaw = tft.euler_matrix(cur_yaw, 0, 0, 'rzyx')
00235         accel_inertial_lateral = np.dot(Ryaw[:3,:3], accel_body)
00236         cur_velocity = self.get_velocity()
00237         # add periodic disturbance:
00238         tmod = self.t % self.periodic_accel_disturbance_period
00239         if tmod <= self.periodic_accel_disturbance_duration:
00240             accel_disturbance = self.periodic_accel_disturbance_vector
00241         else:
00242             accel_disturbance = np.zeros(3)
00243         if self.direct_thrust_commands:
00244             tfactor = cos(radians(pitch_clipped))*cos(radians(roll_clipped))
00245         else:
00246             tfactor = 1.0 # asctec_adapter altitude controller accounts for cosine losses
00247 
00248         accel_inertial =    accel_disturbance + \
00249                             accel_inertial_lateral + \
00250                             np.array([# X:
00251                                       - LINEAR_DRAG*cur_velocity[0]
00252                                       - QUADRATIC_DRAG*np.sign(cur_velocity[0])*(cur_velocity[0]**2),
00253                                       # Y:
00254                                       - LINEAR_DRAG*cur_velocity[1]
00255                                       - QUADRATIC_DRAG*np.sign(cur_velocity[1])*(cur_velocity[1]**2),
00256                                       # Z:
00257                                       GRAVITY - accel_thrust*tfactor
00258                                       - LINEAR_DRAG*cur_velocity[2]
00259                                       - QUADRATIC_DRAG*np.sign(cur_velocity[2])*(cur_velocity[2]**2)])
00260                             
00261         #rospy.loginfo('accel_inertial: ' + str(accel_inertial))
00262         # Add Gaussian noise
00263         if self.gaussian_accel_noise_covariance is not None:
00264             accel_inertial += np.random.multivariate_normal((0,0,0), self.gaussian_accel_noise_covariance)
00265             
00266         sim_debug_msg.accel_inertial = accel_inertial
00267         velocity = cur_velocity + accel_inertial*dt
00268         #rospy.loginfo('velocity: ' + str(velocity))
00269        
00270         # Angles
00271         if self.get_position()[2] >= -IMU_HEIGHT and velocity[2] > 0:
00272             # On the ground..
00273             velocity = np.zeros(3)
00274             orientation = tft.quaternion_from_euler(radians(yaw_cmd), 0, 0, 'rzyx')
00275         else:
00276             # assume perfect yaw rate, and pitch and roll tracking:
00277             orientation = tft.quaternion_from_euler(radians(yaw_cmd), radians(pitch_clipped), radians(roll_clipped), 'rzyx')
00278 
00279         # Position
00280         position = self.get_position() + velocity*dt
00281         position[2] = min(-IMU_HEIGHT, position[2]) # can't go lower than the ground
00282         sim_debug_msg.position = position
00283             
00284         # Angular rate
00285         # from J. Diebel, "Representing attitude: Euler angles, unit quaternions, and rotation vectors," 2006
00286         q_cur = np.array((orientation[3],) + tuple(orientation[:3])) # convert to wxyz 
00287         q_prev = np.array((self.prev_orientation[3],) + tuple(self.prev_orientation[:3])) # convert to wxyz
00288         if dt > EPS:
00289             if q_cur[0]*q_prev[0] < 0:
00290                 mult = -1
00291             else:
00292                 mult = 1
00293             q_dot = QUAT_FILT_A*(mult*q_cur - q_prev)/dt + QUAT_FILT_B*self.q_dot_prev
00294         else:
00295             q_dot = np.zeros(4)  
00296         self.q_dot_prev = q_dot
00297         Wprime = np.array([[-q_cur[1],  q_cur[0],  q_cur[3], -q_cur[2]],
00298                            [-q_cur[2], -q_cur[3],  q_cur[0],  q_cur[1]],
00299                            [-q_cur[3],  q_cur[2], -q_cur[1],  q_cur[0]]])
00300         ang_vel_body = 2*np.dot(Wprime,q_dot)
00301         ang_vel_body[0] = roll_state_new[1]
00302         ang_vel_body[1] = pitch_state_new[1]
00303         R_inertial_body = tft.quaternion_matrix(orientation)[:3,:3]
00304         ang_vel_inertial = np.dot(R_inertial_body, ang_vel_body)
00305         # Assemble new state vector
00306         self.x = np.concatenate([position, velocity, q_cur, ang_vel_inertial])
00307         
00308         # Save for future finite differences
00309         self.prev_orientation = orientation[:] # be sure to copy not reference...
00310         
00311         self.debug_publisher.publish(sim_debug_msg)
00312             
00313     def get_position(self):
00314         return self.x[0:3]
00315             
00316     def get_velocity(self):
00317         return self.x[3:6]
00318             
00319     def get_orientation(self):
00320         """
00321         Return orientation quaternion in (x, y, z, w) convention
00322         """
00323         return tuple(self.x[7:10]) + (self.x[6],)
00324     
00325     def get_angular_velocity(self):
00326         return self.x[10:13]
00327             
00328 
00329         


simulated_quadrotor
Author(s): bouffard
autogenerated on Sun Jan 5 2014 11:38:50