15 from copy
import deepcopy
22 from rospy.numpy_msg
import numpy_msg
23 from geometry_msgs.msg
import WrenchStamped, PoseStamped, TwistStamped, \
24 Vector3, Quaternion, Pose
25 from std_msgs.msg
import Time
26 from nav_msgs.msg
import Odometry
29 quaternion_multiply, quaternion_matrix, quaternion_conjugate, \
31 from uuv_control_msgs.msg
import Trajectory, TrajectoryPoint
33 from uuv_auv_control_allocator.msg
import AUVCommand
35 from .dp_controller_local_planner
import DPControllerLocalPlanner
as LocalPlanner
36 from ._log
import get_logger
40 """General abstract class for DP controllers for underwater vehicles. 41 This is an abstract class, must be inherited by a controller module that 42 overrides the update_controller method. If the controller is set to be 43 model based (is_model_based=True), than the vehicle parameters are going 44 to be read from the ROS parameter server. 48 * `is_model_based` (*type:* `bool`, *default:* `False`): If `True`, the 49 controller uses a model of the vehicle, `False` if it is non-model-based. 50 * `list_odometry_callbacks` (*type:* `list`, *default:* `None`): List of 51 function handles or `lambda` functions that will be called after each 53 * `planner_full_dof` (*type:* `bool`, *default:* `False`): Set the local 54 planner to generate 6 DoF trajectories. Otherwise, the planner will only 55 generate 4 DoF trajectories `(x, y, z, yaw)`. 59 * `use_stamped_poses_only` (*type:* `bool`, *default:* `False`): If `True`, 60 the reference path will be generated with stamped poses only, velocity 61 and acceleration references are set to zero. 62 * `thrusters_only` (*type:* `bool`, *default:* `True`): If `True`, the 63 vehicle only has thrusters as actuators and a thruster manager node is 64 running and the control output will be published as `geometry_msgs/WrenchStamped`. 65 If `False`, the AUV force allocation should be used to compute 66 the control output for each actuator and the output control will be 67 generated as a `uuv_auv_control_allocator.AUVCommand` message. 68 * `saturation` (*type:* `float`, *default:* `5000`): Absolute saturation 69 of the control signal. 70 * `use_auv_control_allocator` (*type:* `bool`, *default:* `False`): If `True`, 71 the output control will be `AUVCommand` message. 72 * `min_thrust` (*type:* `float`, *default:* `40`): Min. thrust set-point output, 73 (parameter only valid for AUVs). 77 * `thruster_output` (*message:* `geometry_msgs/WrenchStamped`): Control set-point 78 for the thruster manager node (requirement: ROS parameters must be `thrusters_only` 79 must be set as `True` and a thruster manager from `uuv_thruster_manager` node must 81 * `auv_command_output` (*message:* `uuv_auv_control_allocator.AUVCommand`): Control 82 set-point for the AUV allocation node (requirement: ROS parameters must be 83 `thrusters_only` must be set as `False` and a AUV control allocation node from 84 `uuv_auv_control_allocator` node must be running). 85 * `reference` (*message:* `uuv_control_msgs/TrajectoryPoint`): Current reference 87 * `error` (*message:* `uuv_control_msgs/TrajectoryPoint`): Current trajectory error. 91 * `reset_controller` (*service:* `uuv_control_msgs/ResetController`): Reset all 92 variables, including error and reference signals. 97 def __init__(self, is_model_based=False, list_odometry_callbacks=None,
98 planner_full_dof=
False):
110 self._logger.info(
'Setting controller as model-based')
112 self._logger.info(
'Setting controller as non-model-based')
115 if rospy.has_param(
'~use_stamped_poses_only'):
124 full_dof=planner_full_dof,
130 if rospy.has_param(
'~saturation'):
133 raise rospy.ROSException(
'Invalid control saturation forces')
140 '~use_auv_control_allocator',
False)
146 'thruster_output', WrenchStamped, queue_size=1)
152 'auv_command_output', AUVCommand, queue_size=1)
163 TrajectoryPoint, queue_size=1)
183 self.
_services[
'reset'] = rospy.Service(
'reset_controller',
193 if list_odometry_callbacks
is not None and \
194 isinstance(list_odometry_callbacks, list):
210 self._logger.info(
'DP controller successfully initialized')
214 while self._logger.handlers:
215 self._logger.handlers.pop()
219 """Create instance of a specific DP controller.""" 220 for controller
in DPControllerBase.__subclasses__():
221 if name == controller.__name__:
222 self._logger.info(
'Creating controller={}'.format(name))
223 return controller(*args)
227 """Return list of DP controllers using this interface.""" 228 return [controller.__name__
for controller
in 229 DPControllerBase.__subclasses__()]
233 """`str`: Identifier name of the controller""" 238 """`bool`: `True` if the first odometry message was received""" 243 """`numpy.array`: Position error wrt world frame""" 244 return np.dot(self._vehicle_model.rotBtoI, self.
_errors[
'pos'])
248 """`numpy.array`: Orientation error""" 249 return deepcopy(self.
_errors[
'rot'][0:3])
253 """`numpy.array`: Orientation error in Euler angles.""" 258 rot = np.array([[1 - 2 * (e2**2 + e3**2),
259 2 * (e1 * e2 - e3 * eta),
260 2 * (e1 * e3 + e2 * eta)],
261 [2 * (e1 * e2 + e3 * eta),
262 1 - 2 * (e1**2 + e3**2),
263 2 * (e2 * e3 - e1 * eta)],
264 [2 * (e1 * e3 - e2 * eta),
265 2 * (e2 * e3 + e1 * eta),
266 1 - 2 * (e1**2 + e2**2)]])
268 roll = np.arctan2(rot[2, 1], rot[2, 2])
270 den = np.sqrt(1 - rot[2, 1]**2)
271 pitch = - np.arctan(rot[2, 1] / max(0.001, den))
273 yaw = np.arctan2(rot[1, 0], rot[0, 0])
274 return np.array([roll, pitch, yaw])
278 """`numpy.array`: Pose error with orientation represented in Euler angles.""" 283 """`numpy.array`: Linear velocity error""" 284 return np.dot(self._vehicle_model.rotBtoI, self.
_errors[
'vel'])
287 msg =
'Dynamic positioning controller\n' 288 msg +=
'Controller= ' + self.
_LABEL +
'\n' 290 msg +=
'Vehicle namespace= ' + self.
_namespace 294 """Create a new instance of a vehicle model. If controller is not model 295 based, this model will have its parameters set to 0 and will be used 296 to receive and transform the odometry data. 301 inertial_frame_id=self._local_planner.inertial_frame_id)
304 """Call the local planner interpolator to retrieve a trajectory 305 point and publish the reference message as `uuv_control_msgs/TrajectoryPoint`. 308 self._local_planner.update_vehicle_pose(
309 self._vehicle_model.pos, self._vehicle_model.quat)
312 reference = self._local_planner.interpolate(t)
314 if reference
is not None:
317 self.
_reference[
'vel'] = np.hstack((reference.v, reference.w))
318 self.
_reference[
'acc'] = np.hstack((reference.a, reference.alpha))
319 if reference
is not None and self._reference_pub.get_num_connections() > 0:
321 msg = TrajectoryPoint()
322 msg.header.stamp = rospy.Time.now()
323 msg.header.frame_id = self._local_planner.inertial_frame_id
324 msg.pose.position = Vector3(*self.
_reference[
'pos'])
325 msg.pose.orientation = Quaternion(*self.
_reference[
'rot'])
326 msg.velocity.linear = Vector3(*self.
_reference[
'vel'][0:3])
327 msg.velocity.angular = Vector3(*self.
_reference[
'vel'][3:6])
328 msg.acceleration.linear = Vector3(*self.
_reference[
'acc'][0:3])
329 msg.acceleration.angular = Vector3(*self.
_reference[
'acc'][3:6])
330 self._reference_pub.publish(msg)
334 """Update time step.""" 340 """Reset reference and and error vectors.""" 350 self.
_errors = dict(pos=np.zeros(3),
355 """Service handler function.""" 357 return ResetControllerResponse(
True)
360 """This function must be implemented by derived classes 361 with the implementation of the control algorithm. 364 raise NotImplementedError()
367 """Update error vectors.""" 369 self._logger.warning(
'Odometry topic has not been update yet')
375 rotItoB = self._vehicle_model.rotItoB
376 rotBtoI = self._vehicle_model.rotBtoI
379 pos = self._vehicle_model.pos
380 vel = self._vehicle_model.vel
381 quat = self._vehicle_model.quat
386 self.
_errors[
'rot'] = quaternion_multiply(
387 quaternion_inverse(quat), self.
_reference[
'rot'])
390 self.
_errors[
'vel'] = np.hstack((
391 np.dot(rotItoB, self.
_reference[
'vel'][0:3]) - vel[0:3],
392 np.dot(rotItoB, self.
_reference[
'vel'][3:6]) - vel[3:6]))
394 if self._error_pub.get_num_connections() > 0:
395 stamp = rospy.Time.now()
396 msg = TrajectoryPoint()
397 msg.header.stamp = stamp
398 msg.header.frame_id = self._local_planner.inertial_frame_id
400 msg.pose.position = Vector3(*np.dot(rotBtoI, self.
_errors[
'pos']))
401 msg.pose.orientation = Quaternion(*self.
_errors[
'rot'])
403 msg.velocity.linear = Vector3(*np.dot(rotBtoI, self.
_errors[
'vel'][0:3]))
404 msg.velocity.angular = Vector3(*np.dot(rotBtoI, self.
_errors[
'vel'][3:6]))
405 self._error_pub.publish(msg)
408 """Publish the thruster manager control set-point. 412 * `force` (*type:* `numpy.array`): 6 DoF control 413 set-point wrench vector 426 surge_speed = self._vehicle_model.vel[0]
430 force_msg = WrenchStamped()
431 force_msg.header.stamp = rospy.Time.now()
432 force_msg.header.frame_id =
'%s/%s' % (self.
_namespace, self._vehicle_model.body_frame_id)
433 force_msg.wrench.force.x = force[0]
434 force_msg.wrench.force.y = force[1]
435 force_msg.wrench.force.z = force[2]
437 force_msg.wrench.torque.x = force[3]
438 force_msg.wrench.torque.y = force[4]
439 force_msg.wrench.torque.z = force[5]
441 self._thrust_pub.publish(force_msg)
444 """Publish the AUV control command message 448 * `surge_speed` (*type:* `float`): Reference surge speed 449 * `wrench` (*type:* `numpy.array`): 6 DoF wrench vector 454 surge_speed = max(0, surge_speed)
457 msg.header.stamp = rospy.Time.now()
458 msg.header.frame_id =
'%s/%s' % (self.
_namespace, self._vehicle_model.body_frame_id)
459 msg.surge_speed = surge_speed
460 msg.command.force.x = max(self.
_min_thrust, wrench[0])
461 msg.command.force.y = wrench[1]
462 msg.command.force.z = wrench[2]
463 msg.command.torque.x = wrench[3]
464 msg.command.torque.y = wrench[4]
465 msg.command.torque.z = wrench[5]
467 self._auv_command_pub.publish(msg)
470 """Odometry topic subscriber callback function. 474 * `msg` (*type:* `nav_msgs/Odometry`): Input odometry 477 self._vehicle_model.update_odometry(msg)
def _update_reference(self)
def publish_control_wrench(self, force)
def error_pose_euler(self)
def error_vel_world(self)
def error_pos_world(self)
def _reset_controller(self)
def _odometry_callback(self, msg)
def reset_controller_callback(self, request)
def _create_vehicle_model(self)
def get_controller(name, args)
def publish_auv_command(self, surge_speed, wrench)
def error_orientation_quat(self)
def update_controller(self)
def error_orientation_rpy(self)
use_auv_control_allocator
_stamp_trajectory_received
def _update_time_step(self)
def __init__(self, is_model_based=False, list_odometry_callbacks=None, planner_full_dof=False)
def get_list_of_controllers()