00001
00002
00003
00004 import roslib
00005 roslib.load_manifest('underwater_vehicle_dynamics')
00006 import rospy
00007 import PyKDL
00008 import sys
00009
00010
00011 from std_msgs.msg import Float64MultiArray
00012 from geometry_msgs.msg import Pose
00013 from geometry_msgs.msg import Quaternion
00014 from geometry_msgs.msg import WrenchStamped
00015
00016
00017 from std_srvs.srv import Empty
00018
00019
00020 from numpy import *
00021 import tf
00022
00023 class Dynamics :
00024
00025
00026 def getConfig(self) :
00027 """ Load parameters from the rosparam server """
00028 self.num_actuators = rospy.get_param(self.vehicle_name+"/num_actuators")
00029
00030 self.period = rospy.get_param(self.vehicle_name + "/dynamics" + "/period")
00031 self.mass = rospy.get_param(self.vehicle_name + "/dynamics" + "/mass")
00032 self.gravity_center = array(rospy.get_param(self.vehicle_name + "/dynamics" + "/gravity_center"))
00033 self.g = rospy.get_param(self.vehicle_name + "/dynamics" + "/g")
00034 self.radius = rospy.get_param(self.vehicle_name + "/dynamics" + "/radius")
00035 self.ctf = rospy.get_param(self.vehicle_name + "/dynamics" + "/ctf")
00036 self.ctb = rospy.get_param(self.vehicle_name + "/dynamics" + "/ctb")
00037 self.actuators_tau = rospy.get_param(self.vehicle_name + "/dynamics" + "/actuators_tau")
00038 self.actuators_maxsat= rospy.get_param(self.vehicle_name + "/dynamics" + "/actuators_maxsat")
00039 self.actuators_minsat = rospy.get_param(self.vehicle_name + "/dynamics" + "/actuators_minsat")
00040 self.actuators_gain = rospy.get_param(self.vehicle_name + "/dynamics" + "/actuators_gain")
00041 self.dzv = rospy.get_param(self.vehicle_name + "/dynamics" + "/dzv")
00042 self.dv = rospy.get_param(self.vehicle_name + "/dynamics" + "/dv")
00043 self.dh = rospy.get_param(self.vehicle_name + "/dynamics" + "/dh")
00044 self.density = rospy.get_param(self.vehicle_name + "/dynamics" + "/density")
00045 self.tensor = array(rospy.get_param(self.vehicle_name + "/dynamics" + "/tensor"))
00046 self.damping = array(rospy.get_param(self.vehicle_name + "/dynamics" + "/damping"))
00047 self.quadratic_damping = array(rospy.get_param(self.vehicle_name + "/dynamics" + "/quadratic_damping"))
00048
00049 self.am=rospy.get_param(self.vehicle_name + "/dynamics"+"/allocation_matrix")
00050
00051 self.p_0 = array(rospy.get_param(self.vehicle_name + "/dynamics" + "/initial_pose"))
00052 self.v_0 = array(rospy.get_param(self.vehicle_name + "/dynamics" + "/initial_velocity"))
00053 self.frame_id = rospy.get_param(self.vehicle_name + "/dynamics" + "/frame_id")
00054 self.external_force_topic = rospy.get_param(self.vehicle_name + "/dynamics" + "/external_force_topic")
00055
00056
00057 self.current_mean = array( rospy.get_param("dynamics/current_mean") )
00058 self.current_sigma = array( rospy.get_param("dynamics/current_sigma") )
00059 self.current_min = array( rospy.get_param("dynamics/current_min") )
00060 self.current_max = array( rospy.get_param("dynamics/current_max") )
00061
00062 self.uwsim_period = rospy.get_param(self.vehicle_name + "/dynamics/uwsim_period")
00063
00064
00065 def s(self, x) :
00066 """ Given a 3D vector computes the 3x3 antisymetric matrix """
00067
00068 ret = array([0.0, -x[2], x[1], x[2], 0.0, -x[0], -x[1], x[0], 0.0 ])
00069 return ret.reshape(3,3)
00070
00071
00072 def generalizedForce(self, du):
00073 """ Computes the generalized force as B*u, being B the allocation matrix and u the control input """
00074 ct = zeros(len(du))
00075 i1 = nonzero(du >= 0.0)
00076 i2 = nonzero(du <= 0.0)
00077 ct[i1] = self.ctf
00078 ct[i2] = self.ctb
00079
00080
00081 b=eval(self.am)
00082 b=array(b).reshape(6,size(b)/6)
00083
00084
00085 t = dot(b, du)
00086 t = squeeze(asarray(t))
00087 return t
00088
00089
00090 def coriolisMatrix(self):
00091 s1 = self.s(dot(self.M[0:3,0:3], self.v[0:3]) + dot(self.M[0:3,3:6], self.v[3:6]))
00092 s2 = self.s(dot(self.M[3:6,0:3], self.v[0:3]) + dot(self.M[3:6,3:6], self.v[3:6]))
00093 c = zeros((6, 6))
00094 c[0:3,3:6] = -s1
00095 c[3:6,0:3] = -s1
00096 c[3:6,3:6] = -s2
00097 return c
00098
00099 def dumpingMatrix(self):
00100
00101 Xu = self.damping[0]
00102 Yv = self.damping[1]
00103 Zw = self.damping[2]
00104 Kp = self.damping[3]
00105 Mq = self.damping[4]
00106 Nr = self.damping[5]
00107
00108
00109 Xuu = self.quadratic_damping[0]
00110 Yvv = self.quadratic_damping[1]
00111 Zww = self.quadratic_damping[2]
00112 Kpp = self.quadratic_damping[3]
00113 Mqq = self.quadratic_damping[4]
00114 Nrr = self.quadratic_damping[5]
00115
00116 d = diag([Xu + Xuu*abs(self.v[0]),
00117 Yv + Yvv*abs(self.v[1]),
00118 Zw + Zww*abs(self.v[2]),
00119 Kp + Kpp*abs(self.v[3]),
00120 Mq + Mqq*abs(self.v[4]),
00121 Nr + Nrr*abs(self.v[5])])
00122 return d
00123
00124 def gravity(self):
00125 """ Computes the gravity and buoyancy forces. Assumes a sphere model for now """
00126
00127 W = self.mass * self.g
00128
00129
00130
00131 if self.p[2] < 0.0:
00132 r = self.radius + self.p[2]
00133 if r < 0.0:
00134 r = 0.0
00135 else :
00136 r = self.radius
00137
00138
00139
00140
00141 F = ((4 * math.pi * pow(r,3))/3)*self.density*self.g
00142
00143
00144 zg = self.gravity_center[2]
00145
00146 g = array([(W - F) * sin(self.p[4]),
00147 -(W - F) * cos(self.p[4]) * sin(self.p[3]),
00148 -(W - F) * cos(self.p[4]) * cos(self.p[3]),
00149 zg*W*cos(self.p[4])*sin(self.p[3]),
00150 zg*W*sin(self.p[4]),
00151 0.0])
00152
00153 return g
00154
00155
00156 def inverseDynamic(self) :
00157 """ Given the setpoint for each thruster, the previous velocity and the
00158 previous position computes the v_dot """
00159 du = self.thrustersDynamics(self.u)
00160 t = self.generalizedForce(du)
00161 c = self.coriolisMatrix()
00162 d = self.dumpingMatrix()
00163 g = self.gravity()
00164 c_v = dot((c-d), self.v)
00165 v_dot = dot(self.IM, (t-c_v-g+self.collisionForce))
00166 v_dot = squeeze(asarray(v_dot))
00167 self.collisionForce=[0,0,0,0,0,0]
00168 return v_dot
00169
00170
00171
00172 def integral(self, x_dot, x, t) :
00173 """ Computes the integral o x dt """
00174 return (x_dot * t) + x
00175
00176
00177 def kinematics(self) :
00178 """ Given the current velocity and the previous position computes the p_dot """
00179 roll = self.p[3]
00180 pitch = self.p[4]
00181 yaw = self.p[5]
00182
00183 rec = [cos(yaw)*cos(pitch), -sin(yaw)*cos(roll)+cos(yaw)*sin(pitch)*sin(roll), sin(yaw)*sin(roll)+cos(yaw)*cos(roll)*sin(pitch),
00184 sin(yaw)*cos(pitch), cos(yaw)*cos(roll)+sin(roll)*sin(pitch)*sin(yaw), -cos(yaw)*sin(roll)+sin(pitch)*sin(yaw)*cos(roll),
00185 -sin(pitch), cos(pitch)*sin(roll), cos(pitch)*cos(roll)]
00186 rec = array(rec).reshape(3,3)
00187
00188 to = [1.0, sin(roll)*tan(pitch), cos(roll)*tan(pitch),
00189 0.0, cos(roll), -sin(roll),
00190 0.0, sin(roll)/cos(pitch), cos(roll)/cos(pitch)]
00191 to = array(to).reshape(3,3)
00192
00193 p_dot = zeros(6)
00194 p_dot[0:3] = dot(rec, self.v[0:3])
00195 p_dot[3:6] = dot(to, self.v[3:6])
00196 return p_dot
00197
00198 def updateThrusters(self, thrusters) :
00199 """Receives the control input, saturates each component to maxsat or minsat, and multiplies each component by the actuator gain"""
00200
00201 t = array(thrusters.data)
00202 for i in range(size(t)):
00203 if t[i]>self.actuators_maxsat[i]:
00204 t[i]=self.actuators_maxsat[i]
00205 elif t[i]<self.actuators_minsat[i]:
00206 t[i]=self.actuators_minsat[i]
00207 self.u=t
00208 for i in range(size(t)):
00209 self.u[i] = self.u[i]*self.actuators_gain[i]
00210
00211
00212 def thrustersDynamics(self, u):
00213 y = zeros(size(u))
00214 for i in range(size(u)):
00215 y[i] = (self.period * u[i] + self.actuators_tau[i] * self.y_1[i]) / (self.period + self.actuators_tau[i])
00216
00217 self.y_1 = y
00218 return y
00219
00220 def updateCollision(self, force) :
00221 self.collisionForce=[force.wrench.force.x,force.wrench.force.y,force.wrench.force.z,force.wrench.torque.x,force.wrench.torque.y,force.wrench.torque.z]
00222
00223 def pubPose(self, event):
00224 pose = Pose()
00225
00226 pose.position.x = self.p[0]
00227 pose.position.y = self.p[1]
00228 pose.position.z = self.p[2]
00229
00230 orientation = tf.transformations.quaternion_from_euler(self.p[3], self.p[4], self.p[5], 'sxyz')
00231 pose.orientation.x = orientation[0]
00232 pose.orientation.y = orientation[1]
00233 pose.orientation.z = orientation[2]
00234 pose.orientation.w = orientation[3]
00235
00236 self.pub_pose.publish(pose)
00237
00238
00239 br = tf.TransformBroadcaster()
00240 br.sendTransform((self.p[0], self.p[1], self.p[2]), orientation,
00241 rospy.Time.now(), "world", str(self.frame_id))
00242
00243 def computeTf(self, tf):
00244 r = PyKDL.Rotation.RPY(math.radians(tf[3]), math.radians(tf[4]), math.radians(tf[5]))
00245 v = PyKDL.Vector(tf[0], tf[1], tf[2])
00246 frame = PyKDL.Frame(r, v)
00247 return frame
00248
00249 def reset(self,req):
00250 self.v = self.v_0
00251 self.p = self.p_0
00252 return []
00253
00254 def __init__(self):
00255 """ Simulates the dynamics of an AUV """
00256
00257 if len(sys.argv) != 6:
00258 sys.exit("Usage: "+sys.argv[0]+" <namespace> <input_topic> <output_topic>")
00259
00260 self.namespace=sys.argv[1]
00261 self.vehicle_name=self.namespace
00262 self.input_topic=sys.argv[2]
00263 self.output_topic=sys.argv[3]
00264
00265
00266 self.collisionForce = [0,0,0,0,0,0]
00267
00268
00269 self.getConfig()
00270
00271 self.y_1 = zeros(5)
00272
00273
00274 self.pub_pose= rospy.Publisher(self.output_topic, Pose)
00275 rospy.init_node("dynamics_"+self.vehicle_name)
00276
00277
00278 self.v = self.v_0
00279 self.p = self.p_0
00280
00281
00282 Ixx = self.tensor[0]
00283 Ixy = self.tensor[1]
00284 Ixz = self.tensor[2]
00285 Iyx = self.tensor[3]
00286 Iyy = self.tensor[4]
00287 Iyz = self.tensor[5]
00288 Izx = self.tensor[6]
00289 Izy = self.tensor[7]
00290 Izz = self.tensor[8]
00291 m = self.mass
00292 xg = self.gravity_center[0]
00293 yg = self.gravity_center[1]
00294 zg = self.gravity_center[2]
00295
00296 Mrb = rospy.get_param(self.vehicle_name + "/dynamics" + "/Mrb")
00297 Mrb = array(Mrb).reshape(6, 6)
00298
00299
00300
00301 Ma = rospy.get_param(self.vehicle_name + "/dynamics" + "/Ma")
00302 Ma = array(Ma).reshape(6, 6)
00303
00304 self.M = Mrb + Ma
00305 self.IM = matrix(self.M).I
00306
00307
00308
00309 random.seed()
00310 self.e_vc = self.current_mean
00311
00312 self.u = array(zeros(self.num_actuators))
00313
00314
00315 rospy.Timer(rospy.Duration(self.uwsim_period), self.pubPose)
00316
00317
00318
00319 rospy.Subscriber(self.input_topic, Float64MultiArray, self.updateThrusters)
00320 rospy.Subscriber(self.external_force_topic, WrenchStamped, self.updateCollision)
00321
00322
00323 s = rospy.Service('/dynamics/reset',Empty, self.reset)
00324
00325 def iterate(self):
00326 t1 = rospy.Time.now()
00327
00328
00329 self.v_dot = self.inverseDynamic()
00330 self.v = self.integral(self.v_dot, self.v, self.period)
00331 self.p_dot = self.kinematics()
00332 self.p = self.integral(self.p_dot, self.p, self.period)
00333
00334 t2 = rospy.Time.now()
00335 p = self.period - (t2-t1).to_sec()
00336 if p < 0.0 : p = 0.0
00337 rospy.sleep(p)
00338
00339
00340 if __name__ == '__main__':
00341 try:
00342 dynamics = Dynamics()
00343 while not rospy.is_shutdown():
00344 dynamics.iterate()
00345
00346 except rospy.ROSInterruptException: pass
00347