15 from __future__
import print_function
18 from nav_msgs.msg
import Odometry
19 from copy
import deepcopy
20 from rospy.numpy_msg
import numpy_msg
22 quaternion_matrix, rotation_matrix, is_same_transform
23 from ._log
import get_logger
32 """Return a cross product operator for the given vector.""" 33 S = np.array([[0, -x[2], x[1]],
40 """Vehicle interface to be used by model-based controllers. It receives the 41 parameters necessary to compute the vehicle's motion according to Fossen's. 47 """Class constructor.""" 48 assert inertial_frame_id
in [
'world',
'world_ned']
67 tf_trans_ned_to_enu = tf_buffer.lookup_transform(
68 'world',
'world_ned', rospy.Time(),
72 [tf_trans_ned_to_enu.transform.rotation.x,
73 tf_trans_ned_to_enu.transform.rotation.y,
74 tf_trans_ned_to_enu.transform.rotation.z,
75 tf_trans_ned_to_enu.transform.rotation.w])
76 except Exception
as ex:
78 'Error while requesting ENU to NED transform' 79 ', message={}'.format(ex))
80 self.
q_ned_to_enu = quaternion_from_euler(2 * np.pi, 0, np.pi)
86 self._logger.info(
'Transform world_ned (NED) to world (ENU)=\n' +
90 if rospy.has_param(
'~mass'):
91 self.
_mass = rospy.get_param(
'~mass')
93 raise rospy.ROSException(
'Mass has to be positive')
95 self.
_inertial = dict(ixx=0, iyy=0, izz=0, ixy=0, ixz=0, iyz=0)
96 if rospy.has_param(
'~inertial'):
97 inertial = rospy.get_param(
'~inertial')
99 if key
not in inertial:
100 raise rospy.ROSException(
'Invalid moments of inertia')
104 if rospy.has_param(
'~cog'):
105 self.
_cog = rospy.get_param(
'~cog')
106 if len(self.
_cog) != 3:
107 raise rospy.ROSException(
'Invalid center of gravity vector')
110 if rospy.has_param(
'~cog'):
111 self.
_cob = rospy.get_param(
'~cob')
112 if len(self.
_cob) != 3:
113 raise rospy.ROSException(
'Invalid center of buoyancy vector')
116 if rospy.has_param(
'~base_link'):
120 if rospy.has_param(
'~volume'):
121 self.
_volume = rospy.get_param(
'~volume')
123 raise rospy.ROSException(
'Invalid volume')
127 if rospy.has_param(
'~density'):
128 self.
_density = rospy.get_param(
'~density')
130 raise rospy.ROSException(
'Invalid fluid density')
136 if rospy.has_param(
'~height'):
137 self.
_height = rospy.get_param(
'~height')
139 raise rospy.ROSException(
'Invalid height')
141 if rospy.has_param(
'~length'):
142 self.
_length = rospy.get_param(
'~length')
144 raise rospy.ROSException(
'Invalid length')
146 if rospy.has_param(
'~width'):
147 self.
_width = rospy.get_param(
'~width')
149 raise rospy.ROSException(
'Invalid width')
153 self.
_M = np.zeros(shape=(6, 6), dtype=float)
154 self.
_M[0:3, 0:3] = self.
_mass * np.eye(3)
155 self.
_M[0:3, 3:6] = - self.
_mass * \
157 self.
_M[3:6, 0:3] = self.
_mass * \
162 self.
_Ma = np.zeros((6, 6))
163 if rospy.has_param(
'~Ma'):
164 self.
_Ma = np.array(rospy.get_param(
'~Ma'))
165 if self._Ma.shape != (6, 6):
166 raise rospy.ROSException(
'Invalid added mass matrix')
176 self.
_C = np.zeros((6, 6))
179 self.
_g = np.zeros(6)
183 if rospy.has_param(
'~linear_damping'):
185 if self._linear_damping.shape == (6,):
187 if self._linear_damping.shape != (6, 6):
188 raise rospy.ROSException(
'Linear damping must be given as a 6x6 matrix or the diagonal coefficients')
192 if rospy.has_param(
'~quad_damping'):
193 self.
_quad_damping = np.array(rospy.get_param(
'~quad_damping'))
194 if self._quad_damping.shape != (6,):
195 raise rospy.ROSException(
'Quadratic damping must be given defined with 6 coefficients')
199 if rospy.has_param(
'~linear_damping_forward_speed'):
201 rospy.get_param(
'~linear_damping_forward_speed'))
202 if self._linear_damping_forward_speed.shape == (6,):
204 if self._linear_damping_forward_speed.shape != (6, 6):
205 raise rospy.ROSException(
206 'Linear damping proportional to the ' 207 'forward speed must be given as a 6x6 ' 208 'matrix or the diagonal coefficients')
211 self.
_D = np.zeros((6, 6))
215 rot=quaternion_from_euler(0, 0, 0))
225 """Convert quaternion into orthogonal rotation matrix. 229 * `q` (*type:* `numpy.array`): Quaternion vector as 234 `numpy.array`: Rotation matrix 240 R = np.array([[1 - 2 * (e2**2 + e3**2),
241 2 * (e1 * e2 - e3 * eta),
242 2 * (e1 * e3 + e2 * eta)],
243 [2 * (e1 * e2 + e3 * eta),
244 1 - 2 * (e1**2 + e3**2),
245 2 * (e2 * e3 - e1 * eta)],
246 [2 * (e1 * e3 - e2 * eta),
247 2 * (e2 * e3 + e1 * eta),
248 1 - 2 * (e1**2 + e2**2)]])
253 """`str`: Return robot namespace.""" 258 """`str`: Body frame ID""" 263 """`str`: Inertial frame ID""" 268 """`float`: Mass in kilograms""" 273 """`float`: Volume of the vehicle in m^3""" 278 """`float`: Magnitude of acceleration of gravity m / s^2""" 283 """`float`: Fluid density as kg / m^3""" 288 """`float`: Height of the vehicle in meters""" 293 """`float`: Width of the vehicle in meters""" 298 """`float`: Length of the vehicle in meters""" 303 """`numpy.array`: Position of the vehicle in meters.""" 304 return deepcopy(self.
_pose[
'pos'])
308 pos = np.array(position)
310 self._logger.error(
'Invalid position vector')
312 self.
_pose[
'pos'] = pos
316 """`numpy.array`: Depth of the vehicle in meters.""" 317 return deepcopy(np.abs(self.
_pose[
'pos'][2]))
321 """`float`: Heading of the vehicle in radians.""" 322 return deepcopy(self.
euler[2])
326 """`numpy.array`: Orientation quaternion as `(qx, qy, qz, qw)`.""" 327 return deepcopy(self.
_pose[
'rot'])
333 self._logger.error(
'Invalid quaternion')
335 self.
_pose[
'rot'] = q_rot
339 """`numpy.array`: Time derivative of the quaternion vector.""" 344 """`numpy.array`: Linear and angular velocity vector.""" 345 return deepcopy(self.
_vel)
349 """Set the velocity vector in the BODY frame.""" 350 v = np.array(velocity)
352 self._logger.error(
'Invalid velocity vector')
358 """`numpy.array`: Linear and angular acceleration vector.""" 359 return deepcopy(self.
_acc)
363 """`list`: Orientation in Euler angles in radians 364 as described in Fossen, 2011. 369 roll = np.arctan2(rot[2, 1], rot[2, 2])
371 den = np.sqrt(1 - rot[2, 1]**2)
372 pitch = - np.arctan(rot[2, 1] / max(0.001, den))
374 yaw = np.arctan2(rot[1, 0], rot[0, 0])
375 return roll, pitch, yaw
379 """`numpy.array`: Time derivative of the Euler 386 """`numpy.array`: Restoring force vector in N.""" 388 return deepcopy(self.
_g)
392 """`numpy.array`: Combined system inertia 393 and added-mass matrices. 399 """`numpy.array`: Combined Coriolis matrix""" 400 return deepcopy(self.
_C)
404 """`numpy.array`: Linear and non-linear damping matrix""" 405 return deepcopy(self.
_D)
409 """`numpy.array`: Pose as a vector, orientation in Euler angles.""" 410 roll, pitch, yaw = self.
euler 420 """`numpy.array`: Pose as a vector, orientation as quaternion.""" 423 pose[3:7] = self.
quat 428 """`numpy.array`: Rotation matrix from INERTIAL to BODY frame""" 429 return self.rotBtoI.T
433 """`numpy.array`: Rotation from BODY to INERTIAL 434 frame using the zyx convention to retrieve Euler 435 angles from the quaternion vector (Fossen, 2011). 443 T = np.array([[1, 0, -np.sin(p)],
444 [0, np.cos(r), np.cos(p) * np.sin(r)],
445 [0, -np.sin(r), np.cos(p) * np.cos(r)]])
452 cp = np.sign(cp) * min(0.001, np.abs(cp))
453 T = 1 / cp * np.array(
454 [[0, np.sin(r) * np.sin(p), np.cos(r) * np.sin(p)],
455 [0, np.cos(r) * np.cos(p), -np.cos(p) * np.sin(r)],
456 [0, np.sin(r), np.cos(r)]])
462 Return matrix for transformation of BODY-fixed angular velocities in the 463 BODY frame in relation to the INERTIAL frame into quaternion rate. 465 e1 = self.
_pose[
'rot'][0]
466 e2 = self.
_pose[
'rot'][1]
467 e3 = self.
_pose[
'rot'][2]
468 eta = self.
_pose[
'rot'][3]
482 return np.array([x[0], -1 * x[1], -1 * x[2]])
483 elif x.shape == (6,):
484 return np.array([x[0], -1 * x[1], -1 * x[2],
485 x[3], -1 * x[4], -1 * x[5]])
486 except Exception
as e:
487 self._logger.error(
'Invalid input vector, v=' + str(x))
488 self._logger.error(
'Message=' + str(e))
497 """Print the vehicle's parameters.""" 498 print(
'Namespace: {}'.format(self.
_namespace))
499 print(
'Mass: {0:.3f} kg'.format(self.
_mass))
500 print(
'System inertia matrix:\n{}'.format(self.
_M))
501 print(
'Added-mass:\n{}'.format(self.
_Ma))
502 print(
'M:\n{}'.format(self.
_Mtotal))
505 print(
'Center of gravity: {}'.format(self.
_cog))
506 print(
'Center of buoyancy: {}'.format(self.
_cob))
514 if vel.shape != (6,):
515 raise rospy.ROSException(
'Velocity vector has the wrong ' 521 self.
_C = np.zeros((6, 6))
524 np.dot(self.
_Mtotal[0:3, 0:3], nu[0:3]) +
525 np.dot(self.
_Mtotal[0:3, 3:6], nu[3:6]))
527 np.dot(self.
_Mtotal[3:6, 0:3], nu[0:3]) +
528 np.dot(self.
_Mtotal[3:6, 3:6], nu[3:6]))
530 self.
_C[0:3, 3:6] = S_12
531 self.
_C[3:6, 0:3] = S_12
532 self.
_C[3:6, 3:6] = S_22
536 if vel.shape != (6,):
537 raise rospy.ROSException(
'Velocity vector has the wrong ' 559 Update the restoring forces for the current orientation. 571 self.
_g = np.zeros(6)
573 self.
_g[0:3] = -1 * np.dot(rotItoB, Fg + Fb)
574 self.
_g[3:6] = -1 * np.dot(rotItoB,
575 np.cross(self.
_cog, Fg) + np.cross(self.
_cob, Fb))
578 """Set added-mass matrix coefficients.""" 579 if Ma.shape != (6, 6):
580 self._logger.error(
'Added mass matrix must have dimensions 6x6')
582 self.
_Ma = np.array(Ma, copy=
True)
587 """Set linear and quadratic damping coefficients.""" 588 if linear_damping.size != 6
or quad_damping.size != 6:
589 self._logger.error(
'Invalid dimensions for damping coefficient vectors')
595 def compute_force(self, acc=None, vel=None, with_restoring=True, use_sname=True):
596 """Return the sum of forces acting on the vehicle. 598 Given acceleration and velocity vectors, this function returns the 599 sum of forces given the rigid-body and hydrodynamic models for the 603 if acc.shape != (6,):
604 raise rospy.ROSException(
'Acceleration vector must have 6 ' 614 if vel.shape != (6,):
615 raise rospy.ROSException(
'Velocity vector must have 6 ' 627 g = deepcopy(self.
_g)
631 f = np.dot(self.
_Mtotal, nu_dot) + np.dot(self.
_C, nu) + \
632 np.dot(self.
_D, nu) + g
640 """Calculate inverse dynamics to obtain the acceleration vector.""" 642 if gen_forces
is not None:
646 if self._Mtotal.sum() == 0:
647 self.
_acc = np.zeros(6)
656 np.dot(self.
_C, nu) -
657 np.dot(self.
_D, nu) -
666 Return the Jacobian for the current orientation using transformations 667 from BODY to INERTIAL frame. 669 jac = np.zeros(shape=(6, 6))
676 """Odometry topic subscriber callback function.""" 685 raise rospy.ROSException(
'The inertial frame ID used by the ' 686 'vehicle model does not match the ' 687 'odometry frame ID, vehicle=%s, odom=%s' %
689 msg.header.frame_id))
693 self.
_pose[
'pos'] = np.array([msg.pose.pose.position.x,
694 msg.pose.pose.position.y,
695 msg.pose.pose.position.z])
698 self.
_pose[
'rot'] = np.array([msg.pose.pose.orientation.x,
699 msg.pose.pose.orientation.y,
700 msg.pose.pose.orientation.z,
701 msg.pose.pose.orientation.w])
703 lin_vel = np.array([msg.twist.twist.linear.x,
704 msg.twist.twist.linear.y,
705 msg.twist.twist.linear.z])
707 lin_vel = np.dot(self.
rotItoB, lin_vel)
709 ang_vel = np.array([msg.twist.twist.angular.x,
710 msg.twist.twist.angular.y,
711 msg.twist.twist.angular.z])
713 ang_vel = np.dot(self.
rotItoB, ang_vel)
715 self.
_vel = np.hstack((lin_vel, ang_vel))
def _update_restoring(self, q=None, use_sname=False)
def _calc_mass_matrix(self)
def _update_damping(self, vel=None)
def restoring_forces(self)
def __init__(self, inertial_frame_id='world')
def _update_coriolis(self, vel=None)
def _calc_inertial_tensor(self)
def update_odometry(self, msg)
def set_damping_coef(self, linear_damping, quad_damping)
def set_added_mass(self, Ma)
def compute_force(self, acc=None, vel=None, with_restoring=True, use_sname=True)
_linear_damping_forward_speed
def compute_acc(self, gen_forces=None, use_sname=True)
def cross_product_operator(x)
def inertial_frame_id(self)