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