00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 
00030 
00031 
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 
00050 MASS = 1.48 
00051 FUDGE = 1500
00052 THRUST_SCALING = MASS*GRAVITY/FUDGE
00053 LINEAR_DRAG = 1.0 
00054 QUADRATIC_DRAG = 0.3 
00055 EPS = 0.0001
00056 IMU_HEIGHT = 0.17 
00057 
00058 
00059 n0 = 40.0
00060 d1 = 8.0
00061 d0 = 80.0
00062 
00063 
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 
00116         
00117         self.q_dot_prev = np.zeros(4)
00118         
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         
00123         self.alt_Ilimit = rospy.get_param("~alt_Ilimit", 400.0)
00124         
00125         
00126         
00127         
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) 
00139         self.roll_bias = rospy.get_param("~roll_bias", 0.0) 
00140         self.roll_slew_rate_limit = rospy.get_param("~roll_slew_rate_limit", 10.0) 
00141         self.pitch_slew_rate_limit = rospy.get_param("~pitch_slew_rate_limit", 10.0) 
00142         temp = rospy.get_param("~gaussian_accel_noise_covariance", None) 
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 
00159         self.roll_cmd = roll_cmd 
00160         self.pitch_cmd = pitch_cmd 
00161         self.direct_yaw_rate_commands = direct_yaw_rate_commands 
00162         self.yaw_cmd = yaw_cmd 
00163         self.yaw_rate_cmd = yaw_rate_cmd 
00164         self.direct_thrust_commands = direct_thrust_commands 
00165         self.alt_cmd = alt_cmd 
00166         self.thrust_cmd = thrust_cmd 
00167         
00168         
00169     def update(self, dt):
00170         
00171         
00172         
00173         self.t += dt
00174         
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         
00178         
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             
00194             A = np.array([[-d1, 1.0],[-d0, 0.0]])
00195             B = np.array([0, n0])
00196             
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 
00202             
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 
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             
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         
00225         
00226         accel_thrust = thrust*THRUST_SCALING/MASS
00227         
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]) 
00233         
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         
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 
00247 
00248         accel_inertial =    accel_disturbance + \
00249                             accel_inertial_lateral + \
00250                             np.array([
00251                                       - LINEAR_DRAG*cur_velocity[0]
00252                                       - QUADRATIC_DRAG*np.sign(cur_velocity[0])*(cur_velocity[0]**2),
00253                                       
00254                                       - LINEAR_DRAG*cur_velocity[1]
00255                                       - QUADRATIC_DRAG*np.sign(cur_velocity[1])*(cur_velocity[1]**2),
00256                                       
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         
00262         
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         
00269        
00270         
00271         if self.get_position()[2] >= -IMU_HEIGHT and velocity[2] > 0:
00272             
00273             velocity = np.zeros(3)
00274             orientation = tft.quaternion_from_euler(radians(yaw_cmd), 0, 0, 'rzyx')
00275         else:
00276             
00277             orientation = tft.quaternion_from_euler(radians(yaw_cmd), radians(pitch_clipped), radians(roll_clipped), 'rzyx')
00278 
00279         
00280         position = self.get_position() + velocity*dt
00281         position[2] = min(-IMU_HEIGHT, position[2]) 
00282         sim_debug_msg.position = position
00283             
00284         
00285         
00286         q_cur = np.array((orientation[3],) + tuple(orientation[:3])) 
00287         q_prev = np.array((self.prev_orientation[3],) + tuple(self.prev_orientation[:3])) 
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         
00306         self.x = np.concatenate([position, velocity, q_cur, ang_vel_inertial])
00307         
00308         
00309         self.prev_orientation = orientation[:] 
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