def _create_vehicle_model(self): """Create a new instance of a vehicle model. If controller is not model based, this model will have its parameters set to 0 and will be used to receive and transform the odometry data. """ if self._vehicle_model is not None: del self._vehicle_model self._vehicle_model = Vehicle( inertial_frame_id=self._local_planner.inertial_frame_id)
def _create_vehicle_model(self): """ Create a new instance of a vehicle model. If controller is not model based, this model will have its parameters set to 0 and will be used to receive and transform the odometry data. """ if self._vehicle_model is not None: del self._vehicle_model self._vehicle_model = Vehicle( inertial_frame_id=self._local_planner.inertial_frame_id)
class DPControllerBase(object): """General abstract class for DP controllers for underwater vehicles. This is an abstract class, must be inherited by a controller module that overrides the update_controller method. If the controller is set to be model based (is_model_based=True), than the vehicle parameters are going to be read from the ROS parameter server. > *Input arguments* * `is_model_based` (*type:* `bool`, *default:* `False`): If `True`, the controller uses a model of the vehicle, `False` if it is non-model-based. * `list_odometry_callbacks` (*type:* `list`, *default:* `None`): List of function handles or `lambda` functions that will be called after each odometry update. * `planner_full_dof` (*type:* `bool`, *default:* `False`): Set the local planner to generate 6 DoF trajectories. Otherwise, the planner will only generate 4 DoF trajectories `(x, y, z, yaw)`. > *ROS parameters* * `use_stamped_poses_only` (*type:* `bool`, *default:* `False`): If `True`, the reference path will be generated with stamped poses only, velocity and acceleration references are set to zero. * `thrusters_only` (*type:* `bool`, *default:* `True`): If `True`, the vehicle only has thrusters as actuators and a thruster manager node is running and the control output will be published as `geometry_msgs/WrenchStamped`. If `False`, the AUV force allocation should be used to compute the control output for each actuator and the output control will be generated as a `uuv_auv_control_allocator.AUVCommand` message. * `saturation` (*type:* `float`, *default:* `5000`): Absolute saturation of the control signal. * `use_auv_control_allocator` (*type:* `bool`, *default:* `False`): If `True`, the output control will be `AUVCommand` message. * `min_thrust` (*type:* `float`, *default:* `40`): Min. thrust set-point output, (parameter only valid for AUVs). > *ROS publishers* * `thruster_output` (*message:* `geometry_msgs/WrenchStamped`): Control set-point for the thruster manager node (requirement: ROS parameters must be `thrusters_only` must be set as `True` and a thruster manager from `uuv_thruster_manager` node must be running). * `auv_command_output` (*message:* `uuv_auv_control_allocator.AUVCommand`): Control set-point for the AUV allocation node (requirement: ROS parameters must be `thrusters_only` must be set as `False` and a AUV control allocation node from `uuv_auv_control_allocator` node must be running). * `reference` (*message:* `uuv_control_msgs/TrajectoryPoint`): Current reference trajectory point. * `error` (*message:* `uuv_control_msgs/TrajectoryPoint`): Current trajectory error. > *ROS services* * `reset_controller` (*service:* `uuv_control_msgs/ResetController`): Reset all variables, including error and reference signals. """ _LABEL = '' def __init__(self, is_model_based=False, list_odometry_callbacks=None, planner_full_dof=False): # Flag will be set to true when all parameters are initialized correctly self._is_init = False self._logger = get_logger() # Reading current namespace self._namespace = rospy.get_namespace() # Configuration for the vehicle dynamic model self._is_model_based = is_model_based if self._is_model_based: self._logger.info('Setting controller as model-based') else: self._logger.info('Setting controller as non-model-based') self._use_stamped_poses_only = False if rospy.has_param('~use_stamped_poses_only'): self._use_stamped_poses_only = rospy.get_param( '~use_stamped_poses_only') # Flag indicating if the vehicle has only thrusters, otherwise # the AUV allocation node will be used self.thrusters_only = rospy.get_param('~thrusters_only', True) # Instance of the local planner for local trajectory generation self._local_planner = LocalPlanner( full_dof=planner_full_dof, stamped_pose_only=self._use_stamped_poses_only, thrusters_only=self.thrusters_only) self._control_saturation = 5000 # TODO: Fix the saturation term and how it is applied if rospy.has_param('~saturation'): self._thrust_saturation = rospy.get_param('~saturation') if self._control_saturation <= 0: raise rospy.ROSException('Invalid control saturation forces') # Flag indicating either use of the AUV control allocator or # direct command of fins and thruster self.use_auv_control_allocator = False if not self.thrusters_only: self.use_auv_control_allocator = rospy.get_param( '~use_auv_control_allocator', False) # Remap the following topics, if needed # Publisher for thruster allocator if self.thrusters_only: self._thrust_pub = rospy.Publisher('thruster_output', WrenchStamped, queue_size=1) else: self._thrust_pub = None if not self.thrusters_only: self._auv_command_pub = rospy.Publisher('auv_command_output', AUVCommand, queue_size=1) else: self._auv_command_pub = None self._min_thrust = rospy.get_param('~min_thrust', 40.0) self._reference_pub = rospy.Publisher('reference', TrajectoryPoint, queue_size=1) # Publish error (for debugging) self._error_pub = rospy.Publisher('error', TrajectoryPoint, queue_size=1) self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6), acc=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) # Time step self._dt = 0 self._prev_time = rospy.get_time() self._services = dict() self._services['reset'] = rospy.Service('reset_controller', ResetController, self.reset_controller_callback) # Time stamp for the received trajectory self._stamp_trajectory_received = rospy.get_time() # Instance of the vehicle model self._vehicle_model = None # If list of callbacks is empty, set the default if list_odometry_callbacks is not None and \ isinstance(list_odometry_callbacks, list): self._odometry_callbacks = list_odometry_callbacks else: self._odometry_callbacks = [ self.update_errors, self.update_controller ] # Initialize vehicle, if model based self._create_vehicle_model() # Flag to indicate that odometry topic is receiving data self._init_odom = False # Subscribe to odometry topic self._odom_topic_sub = rospy.Subscriber('odom', numpy_msg(Odometry), self._odometry_callback) # Stores last simulation time self._prev_t = -1.0 self._logger.info('DP controller successfully initialized') def __del__(self): # Removing logging message handlers while self._logger.handlers: self._logger.handlers.pop() @staticmethod def get_controller(name, *args): """Create instance of a specific DP controller.""" for controller in DPControllerBase.__subclasses__(): if name == controller.__name__: self._logger.info('Creating controller={}'.format(name)) return controller(*args) @staticmethod def get_list_of_controllers(): """Return list of DP controllers using this interface.""" return [ controller.__name__ for controller in DPControllerBase.__subclasses__() ] @property def label(self): """`str`: Identifier name of the controller""" return self._LABEL @property def odom_is_init(self): """`bool`: `True` if the first odometry message was received""" return self._init_odom @property def error_pos_world(self): """`numpy.array`: Position error wrt world frame""" return np.dot(self._vehicle_model.rotBtoI, self._errors['pos']) @property def error_orientation_quat(self): """`numpy.array`: Orientation error""" return deepcopy(self._errors['rot'][0:3]) @property def error_orientation_rpy(self): """`numpy.array`: Orientation error in Euler angles.""" e1 = self._errors['rot'][0] e2 = self._errors['rot'][1] e3 = self._errors['rot'][2] eta = self._errors['rot'][3] rot = np.array([[ 1 - 2 * (e2**2 + e3**2), 2 * (e1 * e2 - e3 * eta), 2 * (e1 * e3 + e2 * eta) ], [ 2 * (e1 * e2 + e3 * eta), 1 - 2 * (e1**2 + e3**2), 2 * (e2 * e3 - e1 * eta) ], [ 2 * (e1 * e3 - e2 * eta), 2 * (e2 * e3 + e1 * eta), 1 - 2 * (e1**2 + e2**2) ]]) # Roll roll = np.arctan2(rot[2, 1], rot[2, 2]) # Pitch, treating singularity cases den = np.sqrt(1 - rot[2, 1]**2) pitch = -np.arctan(rot[2, 1] / max(0.001, den)) # Yaw yaw = np.arctan2(rot[1, 0], rot[0, 0]) return np.array([roll, pitch, yaw]) @property def error_pose_euler(self): """`numpy.array`: Pose error with orientation represented in Euler angles.""" return np.hstack((self._errors['pos'], self.error_orientation_rpy)) @property def error_vel_world(self): """`numpy.array`: Linear velocity error""" return np.dot(self._vehicle_model.rotBtoI, self._errors['vel']) def __str__(self): msg = 'Dynamic positioning controller\n' msg += 'Controller= ' + self._LABEL + '\n' msg += 'Is model based? ' + str(self._is_model_based) + '\n' msg += 'Vehicle namespace= ' + self._namespace return msg def _create_vehicle_model(self): """Create a new instance of a vehicle model. If controller is not model based, this model will have its parameters set to 0 and will be used to receive and transform the odometry data. """ if self._vehicle_model is not None: del self._vehicle_model self._vehicle_model = Vehicle( inertial_frame_id=self._local_planner.inertial_frame_id) def _update_reference(self): """Call the local planner interpolator to retrieve a trajectory point and publish the reference message as `uuv_control_msgs/TrajectoryPoint`. """ # Update the local planner's information about the vehicle's pose self._local_planner.update_vehicle_pose(self._vehicle_model.pos, self._vehicle_model.quat) t = rospy.get_time() reference = self._local_planner.interpolate(t) if reference is not None: self._reference['pos'] = reference.p self._reference['rot'] = reference.q self._reference['vel'] = np.hstack((reference.v, reference.w)) self._reference['acc'] = np.hstack((reference.a, reference.alpha)) if reference is not None and self._reference_pub.get_num_connections( ) > 0: # Publish current reference msg = TrajectoryPoint() msg.header.stamp = rospy.Time.now() msg.header.frame_id = self._local_planner.inertial_frame_id msg.pose.position = Vector3(*self._reference['pos']) msg.pose.orientation = Quaternion(*self._reference['rot']) msg.velocity.linear = Vector3(*self._reference['vel'][0:3]) msg.velocity.angular = Vector3(*self._reference['vel'][3:6]) msg.acceleration.linear = Vector3(*self._reference['acc'][0:3]) msg.acceleration.angular = Vector3(*self._reference['acc'][3:6]) self._reference_pub.publish(msg) return True def _update_time_step(self): """Update time step.""" t = rospy.get_time() self._dt = t - self._prev_time self._prev_time = t def _reset_controller(self): """Reset reference and and error vectors.""" self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6), acc=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) def reset_controller_callback(self, request): """Service handler function.""" self._reset_controller() return ResetControllerResponse(True) def update_controller(self): """This function must be implemented by derived classes with the implementation of the control algorithm. """ # Does nothing, must be overloaded raise NotImplementedError() def update_errors(self): """Update error vectors.""" if not self.odom_is_init: self._logger.warning('Odometry topic has not been update yet') return self._update_reference() # Calculate error in the BODY frame self._update_time_step() # Rotation matrix from INERTIAL to BODY frame rotItoB = self._vehicle_model.rotItoB rotBtoI = self._vehicle_model.rotBtoI if self._dt > 0: # Update position error with respect to the the BODY frame pos = self._vehicle_model.pos vel = self._vehicle_model.vel quat = self._vehicle_model.quat self._errors['pos'] = np.dot(rotItoB, self._reference['pos'] - pos) # Update orientation error self._errors['rot'] = quaternion_multiply(quaternion_inverse(quat), self._reference['rot']) # Velocity error with respect to the the BODY frame self._errors['vel'] = np.hstack( (np.dot(rotItoB, self._reference['vel'][0:3]) - vel[0:3], np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) if self._error_pub.get_num_connections() > 0: stamp = rospy.Time.now() msg = TrajectoryPoint() msg.header.stamp = stamp msg.header.frame_id = self._local_planner.inertial_frame_id # Publish pose error msg.pose.position = Vector3(*np.dot(rotBtoI, self._errors['pos'])) msg.pose.orientation = Quaternion(*self._errors['rot']) # Publish velocity errors in INERTIAL frame msg.velocity.linear = Vector3( *np.dot(rotBtoI, self._errors['vel'][0:3])) msg.velocity.angular = Vector3( *np.dot(rotBtoI, self._errors['vel'][3:6])) self._error_pub.publish(msg) def publish_control_wrench(self, force): """Publish the thruster manager control set-point. > *Input arguments* * `force` (*type:* `numpy.array`): 6 DoF control set-point wrench vector """ if not self.odom_is_init: return # Apply saturation for i in range(6): if force[i] < -self._control_saturation: force[i] = -self._control_saturation elif force[i] > self._control_saturation: force[i] = self._control_saturation if not self.thrusters_only: surge_speed = self._vehicle_model.vel[0] self.publish_auv_command(surge_speed, force) return force_msg = WrenchStamped() force_msg.header.stamp = rospy.Time.now() force_msg.header.frame_id = '%s/%s' % ( self._namespace, self._vehicle_model.body_frame_id) force_msg.wrench.force.x = force[0] force_msg.wrench.force.y = force[1] force_msg.wrench.force.z = force[2] force_msg.wrench.torque.x = force[3] force_msg.wrench.torque.y = force[4] force_msg.wrench.torque.z = force[5] self._thrust_pub.publish(force_msg) def publish_auv_command(self, surge_speed, wrench): """Publish the AUV control command message > *Input arguments* * `surge_speed` (*type:* `float`): Reference surge speed * `wrench` (*type:* `numpy.array`): 6 DoF wrench vector """ if not self.odom_is_init: return surge_speed = max(0, surge_speed) msg = AUVCommand() msg.header.stamp = rospy.Time.now() msg.header.frame_id = '%s/%s' % (self._namespace, self._vehicle_model.body_frame_id) msg.surge_speed = surge_speed msg.command.force.x = max(self._min_thrust, wrench[0]) msg.command.force.y = wrench[1] msg.command.force.z = wrench[2] msg.command.torque.x = wrench[3] msg.command.torque.y = wrench[4] msg.command.torque.z = wrench[5] self._auv_command_pub.publish(msg) def _odometry_callback(self, msg): """Odometry topic subscriber callback function. > *Input arguments* * `msg` (*type:* `nav_msgs/Odometry`): Input odometry message """ self._vehicle_model.update_odometry(msg) if not self._init_odom: self._init_odom = True if len(self._odometry_callbacks): for func in self._odometry_callbacks: func()
class DPControllerBase(object): """General abstract class for DP controllers for underwater vehicles. This is an abstract class, must be inherited by a controller module that overrides the update_controller method. If the controller is set to be model based (is_model_based=True), than the vehicle parameters are going to be read from the ROS parameter server. """ _LABEL = '' def __init__(self, is_model_based=False, list_odometry_callbacks=[], planner_full_dof=False): # Flag will be set to true when all parameters are initialized correctly self._is_init = False self._logger = logging.getLogger('dp_controller') out_hdlr = logging.StreamHandler(sys.stdout) out_hdlr.setFormatter( logging.Formatter( rospy.get_namespace().replace('/', '').upper() + ' -- %(asctime)s | %(levelname)s | %(module)s | %(message)s')) out_hdlr.setLevel(logging.INFO) self._logger.addHandler(out_hdlr) self._logger.setLevel(logging.INFO) # Reading current namespace self._namespace = rospy.get_namespace() # Configuration for the vehicle dynamic model self._is_model_based = is_model_based if self._is_model_based: self._logger.info('Setting controller as model-based') else: self._logger.info('Setting controller as non-model-based') self._use_stamped_poses_only = False if rospy.has_param('~use_stamped_poses_only'): self._use_stamped_poses_only = rospy.get_param( '~use_stamped_poses_only') # Flag indicating if the vehicle has only thrusters, otherwise # the AUV allocation node will be used self.thrusters_only = rospy.get_param('~thrusters_only', True) # Instance of the local planner for local trajectory generation self._local_planner = LocalPlanner( full_dof=planner_full_dof, stamped_pose_only=self._use_stamped_poses_only, thrusters_only=self.thrusters_only) self._control_saturation = 5000 # TODO: Fix the saturation term and how it is applied if rospy.has_param('~saturation'): self._thrust_saturation = rospy.get_param('~saturation') if self._control_saturation <= 0: raise rospy.ROSException('Invalid control saturation forces') # Flag indicating either use of the AUV control allocator or # direct command of fins and thruster self.use_auv_control_allocator = False if not self.thrusters_only: self.use_auv_control_allocator = rospy.get_param( '~use_auv_control_allocator', False) # Remap the following topics, if needed # Publisher for thruster allocator if self.thrusters_only: self._thrust_pub = rospy.Publisher('thruster_output', WrenchStamped, queue_size=1) else: self._thrust_pub = None if not self.thrusters_only: self._auv_command_pub = rospy.Publisher('auv_command_output', AUVCommand, queue_size=1) else: self._auv_command_pub = None self._min_thrust = rospy.get_param('~min_thrust', 40.0) self._reference_pub = rospy.Publisher('reference', TrajectoryPoint, queue_size=1) # Publish error (for debugging) self._error_pub = rospy.Publisher('error', TrajectoryPoint, queue_size=1) self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6), acc=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) # Time step self._dt = 0 self._prev_time = rospy.get_time() self._services = dict() self._services['reset'] = rospy.Service('reset_controller', ResetController, self.reset_controller_callback) # Time stamp for the received trajectory self._stamp_trajectory_received = rospy.get_time() # Instance of the vehicle model self._vehicle_model = None # If list of callbacks is empty, set the default if len(list_odometry_callbacks): self._odometry_callbacks = list_odometry_callbacks else: self._odometry_callbacks = [ self.update_errors, self.update_controller ] # Initialize vehicle, if model based self._create_vehicle_model() # Flag to indicate that odometry topic is receiving data self._init_odom = False # Subscribe to odometry topic self._odom_topic_sub = rospy.Subscriber('/anahita/pose_gt', numpy_msg(Odometry), self._odometry_callback) # Stores last simulation time self._prev_t = -1.0 self._logger.info('DP controller successfully initialized') def __del__(self): # Removing logging message handlers while self._logger.handlers: self._logger.handlers.pop() @staticmethod def get_controller(name, *args): """Create instance of a specific DP controller.""" for controller in DPControllerBase.__subclasses__(): if name == controller.__name__: print 'Creating controller=', name return controller(*args) @staticmethod def get_list_of_controllers(): """Return list of DP controllers using this interface.""" return [ controller.__name__ for controller in DPControllerBase.__subclasses__() ] @property def label(self): return self._LABEL @property def odom_is_init(self): return self._init_odom @property def error_pos_world(self): return np.dot(self._vehicle_model.rotBtoI, self._errors['pos']) @property def error_orientation_quat(self): return deepcopy(self._errors['rot'][0:3]) @property def error_orientation_rpy(self): """Return orientation error in Euler angles.""" e1 = self._errors['rot'][0] e2 = self._errors['rot'][1] e3 = self._errors['rot'][2] eta = self._errors['rot'][3] rot = np.array([[ 1 - 2 * (e2**2 + e3**2), 2 * (e1 * e2 - e3 * eta), 2 * (e1 * e3 + e2 * eta) ], [ 2 * (e1 * e2 + e3 * eta), 1 - 2 * (e1**2 + e3**2), 2 * (e2 * e3 - e1 * eta) ], [ 2 * (e1 * e3 - e2 * eta), 2 * (e2 * e3 + e1 * eta), 1 - 2 * (e1**2 + e2**2) ]]) # Roll roll = np.arctan2(rot[2, 1], rot[2, 2]) # Pitch, treating singularity cases den = np.sqrt(1 - rot[2, 1]**2) pitch = -np.arctan(rot[2, 1] / max(0.001, den)) # Yaw yaw = np.arctan2(rot[1, 0], rot[0, 0]) return np.array([roll, pitch, yaw]) @property def error_pose_euler(self): """Pose error with orientation represented in Euler angles.""" return np.hstack((self._errors['pos'], self.error_orientation_rpy)) @property def error_vel_world(self): return np.dot(self._vehicle_model.rotBtoI, self._errors['vel']) def __str__(self): msg = 'Dynamic positioning controller\n' msg += 'Controller= ' + self._LABEL + '\n' msg += 'Is model based? ' + str(self._is_model_based) + '\n' msg += 'Vehicle namespace= ' + self._namespace return msg def _create_vehicle_model(self): """ Create a new instance of a vehicle model. If controller is not model based, this model will have its parameters set to 0 and will be used to receive and transform the odometry data. """ if self._vehicle_model is not None: del self._vehicle_model self._vehicle_model = Vehicle( inertial_frame_id=self._local_planner.inertial_frame_id) def _update_reference(self): # Update the local planner's information about the vehicle's pose self._local_planner.update_vehicle_pose(self._vehicle_model.pos, self._vehicle_model.quat) t = rospy.get_time() reference = self._local_planner.interpolate(t) if reference is not None: self._reference['pos'] = reference.p self._reference['rot'] = reference.q self._reference['vel'] = np.hstack((reference.v, reference.w)) self._reference['acc'] = np.hstack((reference.a, reference.alpha)) # print '------------------ REFERENCE\n' # print reference if reference is not None and self._reference_pub.get_num_connections( ) > 0: # Publish current reference msg = TrajectoryPoint() msg.header.stamp = rospy.Time.now() msg.header.frame_id = self._local_planner.inertial_frame_id msg.pose.position = Vector3(*self._reference['pos']) msg.pose.orientation = Quaternion(*self._reference['rot']) msg.velocity.linear = Vector3(*self._reference['vel'][0:3]) msg.velocity.angular = Vector3(*self._reference['vel'][3:6]) msg.acceleration.linear = Vector3(*self._reference['acc'][0:3]) msg.acceleration.angular = Vector3(*self._reference['acc'][3:6]) self._reference_pub.publish(msg) return True def _update_time_step(self): t = rospy.get_time() self._dt = t - self._prev_time self._prev_time = t def _reset_controller(self): self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6), acc=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) def reset_controller_callback(self, request): self._reset_controller() return ResetControllerResponse(True) def update_controller(self): # Does nothing, must be overloaded raise NotImplementedError() def update_errors(self): if not self.odom_is_init: self._logger.warning('Odometry topic has not been update yet') return self._update_reference() # Calculate error in the BODY frame self._update_time_step() # Rotation matrix from INERTIAL to BODY frame rotItoB = self._vehicle_model.rotItoB rotBtoI = self._vehicle_model.rotBtoI if self._dt > 0: # Update position error with respect to the the BODY frame pos = self._vehicle_model.pos vel = self._vehicle_model.vel quat = self._vehicle_model.quat self._errors['pos'] = np.dot(rotItoB, self._reference['pos'] - pos) # Update orientation error self._errors['rot'] = quaternion_multiply(quaternion_inverse(quat), self._reference['rot']) # Velocity error with respect to the the BODY frame self._errors['vel'] = np.hstack( (np.dot(rotItoB, self._reference['vel'][0:3]) - vel[0:3], np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) if self._error_pub.get_num_connections() > 0: stamp = rospy.Time.now() msg = TrajectoryPoint() msg.header.stamp = stamp msg.header.frame_id = self._local_planner.inertial_frame_id # Publish pose error msg.pose.position = Vector3(*np.dot(rotBtoI, self._errors['pos'])) msg.pose.orientation = Quaternion(*self._errors['rot']) # Publish velocity errors in INERTIAL frame msg.velocity.linear = Vector3( *np.dot(rotBtoI, self._errors['vel'][0:3])) msg.velocity.angular = Vector3( *np.dot(rotBtoI, self._errors['vel'][3:6])) self._error_pub.publish(msg) def publish_control_wrench(self, force): if not self.odom_is_init: return # Apply saturation for i in range(6): if force[i] < -self._control_saturation: force[i] = -self._control_saturation elif force[i] > self._control_saturation: force[i] = self._control_saturation # if not self.thrusters_only: # surge_speed = self._vehicle_model.vel[0] # self.publish_auv_command(surge_speed, force) # return force_msg = WrenchStamped() force_msg.header.stamp = rospy.Time.now() force_msg.header.frame_id = '%s/%s' % ( self._namespace, self._vehicle_model.body_frame_id) force_msg.wrench.force.x = force[0] force_msg.wrench.force.y = force[1] force_msg.wrench.force.z = force[2] force_msg.wrench.torque.x = force[3] force_msg.wrench.torque.y = force[4] force_msg.wrench.torque.z = force[5] self._thrust_pub.publish(force_msg) def publish_auv_command(self, surge_speed, wrench): if not self.odom_is_init: return surge_speed = max(0, surge_speed) msg = AUVCommand() msg.header.stamp = rospy.Time.now() msg.header.frame_id = '%s/%s' % (self._namespace, self._vehicle_model.body_frame_id) msg.surge_speed = surge_speed msg.command.force.x = max(self._min_thrust, wrench[0]) msg.command.force.y = wrench[1] msg.command.force.z = wrench[2] msg.command.torque.x = wrench[3] msg.command.torque.y = wrench[4] msg.command.torque.z = wrench[5] self._auv_command_pub.publish(msg) def _odometry_callback(self, msg): """Odometry topic subscriber callback function.""" # rospy.loginfo(msg) self._vehicle_model.update_odometry(msg) if not self._init_odom: self._init_odom = True if len(self._odometry_callbacks): for func in self._odometry_callbacks: func()
class DPControllerBase1(object): """General abstract class for DP controllers for underwater vehicles. This is an abstract class, must be inherited by a controller module that overrides the update_controller method. If the controller is set to be model based (is_model_based=True), than the vehicle parameters are going to be read from the ROS parameter server. > *Input arguments* * `is_model_based` (*type:* `bool`, *default:* `False`): If `True`, the controller uses a model of the vehicle, `False` if it is non-model-based. * `list_odometry_callbacks` (*type:* `list`, *default:* `None`): List of function handles or `lambda` functions that will be called after each odometry update. * `planner_full_dof` (*type:* `bool`, *default:* `False`): Set the local planner to generate 6 DoF trajectories. Otherwise, the planner will only generate 4 DoF trajectories `(x, y, z, yaw)`. > *ROS parameters* * `use_stamped_poses_only` (*type:* `bool`, *default:* `False`): If `True`, the reference path will be generated with stamped poses only, velocity and acceleration references are set to zero. * `thrusters_only` (*type:* `bool`, *default:* `True`): If `True`, the vehicle only has thrusters as actuators and a thruster manager node is running and the control output will be published as `geometry_msgs/WrenchStamped`. If `False`, the AUV force allocation should be used to compute the control output for each actuator and the output control will be generated as a `uuv_auv_control_allocator.AUVCommand` message. * `saturation` (*type:* `float`, *default:* `5000`): Absolute saturation of the control signal. * `use_auv_control_allocator` (*type:* `bool`, *default:* `False`): If `True`, the output control will be `AUVCommand` message. * `min_thrust` (*type:* `float`, *default:* `40`): Min. thrust set-point output, (parameter only valid for AUVs). > *ROS publishers* * `thruster_output` (*message:* `geometry_msgs/WrenchStamped`): Control set-point for the thruster manager node (requirement: ROS parameters must be `thrusters_only` must be set as `True` and a thruster manager from `uuv_thruster_manager` node must be running). * `auv_command_output` (*message:* `uuv_auv_control_allocator.AUVCommand`): Control set-point for the AUV allocation node (requirement: ROS parameters must be `thrusters_only` must be set as `False` and a AUV control allocation node from `uuv_auv_control_allocator` node must be running). * `reference` (*message:* `uuv_control_msgs/TrajectoryPoint`): Current reference trajectory point. * `error` (*message:* `uuv_control_msgs/TrajectoryPoint`): Current trajectory error. > *ROS services* * `reset_controller` (*service:* `uuv_control_msgs/ResetController`): Reset all variables, including error and reference signals. """ _LABEL = '' def __init__(self, is_model_based=False, list_odometry_callbacks=None, planner_full_dof=False): # Flag will be set to true when all parameters are initialized correctly self._is_init = False self._logger = get_logger() # Reading current namespace self._namespace = rospy.get_namespace() # Configuration for the vehicle dynamic model self._is_model_based = is_model_based if self._is_model_based: self._logger.info('Setting controller as model-based') else: self._logger.info('Setting controller as non-model-based') self._use_stamped_poses_only = False if rospy.has_param('~use_stamped_poses_only'): self._use_stamped_poses_only = rospy.get_param( '~use_stamped_poses_only') # Flag indicating if the vehicle has only thrusters, otherwise # the AUV allocation node will be used self.thrusters_only = rospy.get_param('~thrusters_only', True) # Instance of the local planner for local trajectory generation self._local_planner = LocalPlanner( full_dof=planner_full_dof, stamped_pose_only=self._use_stamped_poses_only, thrusters_only=self.thrusters_only) self._control_saturation = 150000 # TODO: Fix the saturation term and how it is applied if rospy.has_param('~saturation'): self._thrust_saturation = rospy.get_param('~saturation') if self._control_saturation <= 0: raise rospy.ROSException('Invalid control saturation forces') # Flag indicating either use of the AUV control allocator or # direct command of fins and thruster self.use_auv_control_allocator = False if not self.thrusters_only: self.use_auv_control_allocator = rospy.get_param( '~use_auv_control_allocator', False) # Remap the following topics, if needed # Publisher for thruster allocator if self.thrusters_only: self._thrust_pub = rospy.Publisher('thruster_output', WrenchStamped, queue_size=1) else: self._thrust_pub = None if not self.thrusters_only: self._auv_command_pub = rospy.Publisher('auv_command_output', AUVCommand, queue_size=1) else: self._auv_command_pub = None self._min_thrust = rospy.get_param('~min_thrust', 40.0) self._reference_pub = rospy.Publisher('reference', TrajectoryPointObjectFollow, queue_size=1) # Publish error (for debugging) self._error_pub = rospy.Publisher('error', TrajectoryPointObjectFollow, queue_size=1) self._init_reference = False self._equivalentControl_pub = rospy.Publisher('equivalentControl', Vector6, queue_size=10) self._generalForce_pub = rospy.Publisher('generalForce', Vector6, queue_size=10) self._restoring_pub = rospy.Publisher('restoring', Vector6, queue_size=10) self._sliding_pub = rospy.Publisher('slidingSurface', WrenchStamped, queue_size=1) self._count_pub = rospy.Publisher('count', Int32, queue_size=10) self._dt_pub = rospy.Publisher('dt', Float32, queue_size=10) self._dt_pub1 = rospy.Publisher('dt1', Float32, queue_size=10) #self._ref_u_pub=rospy.Publisher('rexf_u', Float32, queue_size=10) #self._ref_v_pub=rospy.Publisher('ref_v', Float32, queue_size=10) #self._ref_w_pub=rospy.Publisher('ref_w', Float32, queue_size=10) #self._veh_u_pub=rospy.Publisher('veh_u', Float32, queue_size=10) #self._veh_v_pub=rospy.Publisher('veh_v', Float32, queue_size=10) #self._veh_w_pub=rospy.Publisher('veh_w', Float32, queue_size=10) #self._error_up_pub=rospy.Publisher('error_up', Float32, queue_size=10) #self._surface_up_pub=rospy.Publisher('surface_up', Float32, queue_size=10) #self._force_up_pub=rospy.Publisher('force_up', Float32, queue_size=10) self._MParameter_pub = rospy.Publisher('MParameter', Matrix6, queue_size=10) self._CParameter_pub = rospy.Publisher('CParameter', Matrix6, queue_size=10) self._DParameter_pub = rospy.Publisher('DParameter', Matrix6, queue_size=10) self._vel_pub = rospy.Publisher('vel', Vector6, queue_size=10) self._count = 0 self._imu_topic_sub = rospy.Subscriber('/rexrov2/imu', Imu, self.imuCallback) # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) self._errors_refDelay = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) # Time step self._dt = 0 self._prev_time = rospy.get_time() self._services = dict() self._services['reset'] = rospy.Service('reset_controller', ResetController, self.reset_controller_callback) # Time stamp for the received trajectory self._stamp_trajectory_received = rospy.get_time() # Instance of the vehicle model self._vehicle_model = None # If list of callbacks is empty, set the default if list_odometry_callbacks is not None and \ isinstance(list_odometry_callbacks, list): self._odometry_callbacks = list_odometry_callbacks else: self._odometry_callbacks = [ self.update_errors, self.update_controller ] # Initialize vehicle, if model based self._create_vehicle_model() # Flag to indicate that odometry topic is receiving data self._init_odom = False # Subscribe to odometry topic self._odom_topic_sub = rospy.Subscriber('odom', numpy_msg(Odometry), self._odometry_callback) # Stores last simulation time self._prev_t = -1.0 self._logger.info('DP controller successfully initialized') self._boxOdom_topic_sub = rospy.Subscriber('/ground_truth/stateRexRov', Odometry, self.objectCallback) #self.ref_boxPosition=np.array([0, 0, 0]); self.ref_boxPosition = np.zeros(3) self.ref_boxOrientation = np.array([0, 0, 0, 0]) self.ref_boxVelocityLinear = np.array([0, 0, 0]) #self.ref_boxVelocityLinear=np.zeros(3) #self.ref_boxVelocityLinear1=np.array([0, 0, 0]); #self.ref_boxPosition_prev=np.array([0, 0, 0]); self.ref_boxVelocityLinear1 = np.zeros(3) self.ref_boxPosition_prev = np.zeros(3) self._prev_t = 0 self.ref_boxVelocityAngular = np.zeros(3) #self.ref_boxVelocityAngular=np.array([0,0,0]) self.object_position = np.array([0, 0, 0]) self.object_orientation = np.array([0, 0, 0, 0]) self.q = Quaternion self.object_velocityLinear = np.array([0, 0, 0]) self.object_velocityAngular = np.zeros(3) self.pose_xytheta = Pose self.x_wg = Float32() self.y_wg = Float32() self.theta_wg = Float32() self.pose_vehicle = np.array([0, 0, 0]) self.imuAccLinear = np.array([0, 0, 0]) self._linear_acceleration = np.zeros(3) self.pos_ref_prev1 = np.array([0, 0, -15]) self.pos_ref_prev2 = np.array([0, 0, -15]) self.pos_ref_prev3 = np.array([0, 0, -15]) self.pos_ref_prev4 = np.array([0, 0, -15]) self.pos_ref_prev5 = np.array([0, 0, -15]) self.pos_ref_prev6 = np.array([0, 0, -15]) self.pos_ref_prev7 = np.array([0, 0, -15]) self.pos_ref_prev8 = np.array([0, 0, -15]) self.pos_ref_prev9 = np.array([0, 0, -15]) self.pos_ref_prev10 = np.array([0, 0, -15]) self.pos_ref_prev11 = np.array([0, 0, -15]) self.pos_ref_prev12 = np.array([0, 0, -15]) self.pos_ref_prev13 = np.array([0, 0, -15]) self.pos_ref_prev14 = np.array([0, 0, -15]) self.rot_ref_prev1 = np.zeros(4) self.rot_ref_prev2 = np.zeros(4) self.rot_ref_prev3 = np.zeros(4) self.rot_ref_prev4 = np.zeros(4) self.rot_ref_prev5 = np.zeros(4) self.rot_ref_prev6 = np.zeros(4) self.rot_ref_prev7 = np.zeros(4) self.rot_ref_prev8 = np.zeros(4) self.rot_ref_prev9 = np.zeros(4) self.rot_ref_prev10 = np.zeros(4) self.rot_ref_prev11 = np.zeros(4) self.rot_ref_prev12 = np.zeros(4) self.rot_ref_prev13 = np.zeros(4) self.rot_ref_prev14 = np.zeros(4) self.vel_ref_prev1 = np.zeros(6) self.vel_ref_prev2 = np.zeros(6) self.vel_ref_prev3 = np.zeros(6) self.vel_ref_prev4 = np.zeros(6) self.vel_ref_prev5 = np.zeros(6) self.vel_ref_prev6 = np.zeros(6) self.vel_ref_prev7 = np.zeros(6) self.vel_ref_prev8 = np.zeros(6) self.vel_ref_prev9 = np.zeros(6) self.vel_ref_prev10 = np.zeros(6) self.vel_ref_prev11 = np.zeros(6) self.vel_ref_prev12 = np.zeros(6) self.vel_ref_prev13 = np.zeros(6) self.vel_ref_prev14 = np.zeros(6) self.pos_veh_prev1 = np.array([-7, 0, -15]) self.pos_veh_prev2 = np.array([-7, 0, -15]) self.pos_veh_prev3 = np.array([-7, 0, -15]) self.pos_veh_prev4 = np.array([-7, 0, -15]) self.pos_veh_prev5 = np.array([-7, 0, -15]) self.pos_veh_prev6 = np.array([-7, 0, -15]) self.pos_veh_prev7 = np.array([-7, 0, -15]) self.vel_veh_prev1 = np.zeros(6) self.vel_veh_prev2 = np.zeros(6) self.vel_veh_prev3 = np.zeros(6) self.vel_veh_prev4 = np.zeros(6) self.vel_veh_prev5 = np.zeros(6) self.vel_veh_prev6 = np.zeros(6) self.vel_veh_prev7 = np.zeros(6) self.quat_veh_prev1 = np.array([0, 0, 0, 0.994]) self.quat_veh_prev2 = np.array([0, 0, 0, 0.994]) self.quat_veh_prev3 = np.array([0, 0, 0, 0.994]) self.quat_veh_prev4 = np.array([0, 0, 0, 0.994]) self.quat_veh_prev5 = np.array([0, 0, 0, 0.994]) self.quat_veh_prev6 = np.array([0, 0, 0, 0.994]) self.quat_veh_prev7 = np.array([0, 0, 0, 0.994]) def __del__(self): # Removing logging message handlers while self._logger.handlers: self._logger.handlers.pop() @staticmethod def get_controller(name, *args): """Create instance of a specific DP controller.""" for controller in DPControllerBase1.__subclasses__(): if name == controller.__name__: self._logger.info('Creating controller={}'.format(name)) return controller(*args) @staticmethod def get_list_of_controllers(): """Return list of DP controllers using this interface.""" return [ controller.__name__ for controller in DPControllerBase1.__subclasses__() ] @property def label(self): """`str`: Identifier name of the controller""" return self._LABEL @property def odom_is_init(self): """`bool`: `True` if the first odometry message was received""" return self._init_odom @property def error_pos_world(self): """`numpy.array`: Position error wrt world frame""" return np.dot(self._vehicle_model.rotBtoI, self._errors['pos']) @property def error_orientation_quat(self): """`numpy.array`: Orientation error""" return deepcopy(self._errors['rot'][0:3]) @property def error_orientation_rpy(self): """`numpy.array`: Orientation error in Euler angles.""" e1 = self._errors['rot'][0] e2 = self._errors['rot'][1] e3 = self._errors['rot'][2] eta = self._errors['rot'][3] rot = np.array([[ 1 - 2 * (e2**2 + e3**2), 2 * (e1 * e2 - e3 * eta), 2 * (e1 * e3 + e2 * eta) ], [ 2 * (e1 * e2 + e3 * eta), 1 - 2 * (e1**2 + e3**2), 2 * (e2 * e3 - e1 * eta) ], [ 2 * (e1 * e3 - e2 * eta), 2 * (e2 * e3 + e1 * eta), 1 - 2 * (e1**2 + e2**2) ]]) # Roll roll = np.arctan2(rot[2, 1], rot[2, 2]) # Pitch, treating singularity cases den = np.sqrt(1 - rot[2, 1]**2) pitch = -np.arctan(rot[2, 1] / max(0.001, den)) # Yaw yaw = np.arctan2(rot[1, 0], rot[0, 0]) return np.array([roll, pitch, yaw]) @property def error_pose_euler(self): """`numpy.array`: Pose error with orientation represented in Euler angles.""" return np.hstack((self._errors['pos'], self.error_orientation_rpy)) @property def error_vel_world(self): """`numpy.array`: Linear velocity error""" return np.dot(self._vehicle_model.rotBtoI, self._errors['vel']) def __str__(self): msg = 'Dynamic positioning controller\n' msg += 'Controller= ' + self._LABEL + '\n' msg += 'Is model based? ' + str(self._is_model_based) + '\n' msg += 'Vehicle namespace= ' + self._namespace return msg def _create_vehicle_model(self): """Create a new instance of a vehicle model. If controller is not model based, this model will have its parameters set to 0 and will be used to receive and transform the odometry data. """ if self._vehicle_model is not None: del self._vehicle_model self._vehicle_model = Vehicle( inertial_frame_id=self._local_planner.inertial_frame_id) def objectCallback(self, data): msg = Pose() msg.position.x = self._vehicle_model._pose['pos'][0] msg.position.y = self._vehicle_model._pose['pos'][1] msg.position.z = self._vehicle_model._pose['pos'][2] msg.orientation.x = self._vehicle_model._pose['rot'][0] msg.orientation.y = self._vehicle_model._pose['rot'][1] msg.orientation.z = self._vehicle_model._pose['rot'][2] msg.orientation.w = self._vehicle_model._pose['rot'][3] self.pose_vehicle = geo_maths.pose_to_xytheta(msg) self.pose = geo_maths.pose_to_xytheta(data.pose.pose) T_wb = geo_maths.xytheta_to_T(self.pose[0], self.pose[1], self.pose[2]) T_bg = geo_maths.xytheta_to_T(13, 0, 0) T_wg = np.dot(T_wb, T_bg) self.x_wg.data, self.y_wg.data, self.theta_wg.data = geo_maths.T_to_xytheta( T_wg) self.q = geo_maths.theta_to_quaternion(self.theta_wg.data) self.ref_boxPosition[0] = self.x_wg.data self.ref_boxPosition[1] = self.y_wg.data #self.ref_boxOrientation[0]=q.x #self.ref_boxOrientation[1]=q.y #self.ref_boxOrientation[2]=q.z #self.ref_boxOrientation[3]=q.w self.object_position = data.pose.pose.position self.object_orientation = data.pose.pose.orientation self.object_velocityLinear = data.twist.twist.linear self.object_velocityAngular = data.twist.twist.angular #self.ref_boxPosition[0]=self.object_position.x #self.ref_boxPosition[1]=self.object_position.y self.ref_boxPosition[2] = self.object_position.z self.ref_boxOrientation[0] = self.object_orientation.x self.ref_boxOrientation[1] = self.object_orientation.y self.ref_boxOrientation[2] = self.object_orientation.z self.ref_boxOrientation[3] = self.object_orientation.w self.ref_boxVelocityLinear[0] = self.object_velocityLinear.x self.ref_boxVelocityLinear[1] = self.object_velocityLinear.y self.ref_boxVelocityLinear[2] = self.object_velocityLinear.z t = rospy.Time.now().to_sec() dt = t - self._prev_t if self._prev_t < 0.0: dt = 0.05 ref_position = np.hstack( (self.x_wg.data, self.y_wg.data, self.object_position.z)) self.ref_boxVelocityLinear1 = ( ref_position - self.ref_boxPosition_prev) / max(0.01, dt) self.ref_boxPosition_prev = ref_position self.ref_boxVelocityAngular[0] = self.object_velocityAngular.x self.ref_boxVelocityAngular[1] = self.object_velocityAngular.y self.ref_boxVelocityAngular[2] = self.object_velocityAngular.z def _update_reference(self): """Call the local planner interpolator to retrieve a trajectory point and publish the reference message as `uuv_control_msgs/TrajectoryPoint`. """ # Update the local planner's information about the vehicle's pose self._local_planner.update_vehicle_pose(self._vehicle_model.pos, self._vehicle_model.quat) t = rospy.get_time() reference = self._local_planner.interpolate(t) if reference is not None: # self._reference['pos'] = reference.p # self._reference['rot'] = reference.q # self._reference['vel'] = np.hstack((reference.v, reference.w)) self._reference['pos'] = self.ref_boxPosition self._reference['rot'] = [self.q.x, self.q.y, self.q.z, self.q.w] self._reference['vel'] = np.hstack( (self.ref_boxVelocityLinear1, self.ref_boxVelocityAngular)) pos_ref_delay1 = self.pos_ref_prev1 pos_ref_delay2 = self.pos_ref_prev2 pos_ref_delay3 = self.pos_ref_prev3 pos_ref_delay4 = self.pos_ref_prev4 pos_ref_delay5 = self.pos_ref_prev5 pos_ref_delay6 = self.pos_ref_prev6 pos_ref_delay7 = self.pos_ref_prev7 pos_ref_delay8 = self.pos_ref_prev8 pos_ref_delay9 = self.pos_ref_prev9 pos_ref_delay10 = self.pos_ref_prev10 pos_ref_delay11 = self.pos_ref_prev11 pos_ref_delay12 = self.pos_ref_prev12 pos_ref_delay13 = self.pos_ref_prev13 pos_ref_delay14 = self.pos_ref_prev14 self.pos_ref_prev1 = self._reference['pos'] self.pos_ref_prev2 = pos_ref_delay1 self.pos_ref_prev3 = pos_ref_delay2 self.pos_ref_prev4 = pos_ref_delay3 self.pos_ref_prev5 = pos_ref_delay4 self.pos_ref_prev6 = pos_ref_delay5 self.pos_ref_prev7 = pos_ref_delay6 self.pos_ref_prev8 = pos_ref_delay7 self.pos_ref_prev9 = pos_ref_delay8 self.pos_ref_prev10 = pos_ref_delay9 self.pos_ref_prev11 = pos_ref_delay10 self.pos_ref_prev12 = pos_ref_delay11 self.pos_ref_prev13 = pos_ref_delay12 self.pos_ref_prev14 = pos_ref_delay13 rot_ref_delay1 = self.rot_ref_prev1 rot_ref_delay2 = self.rot_ref_prev2 rot_ref_delay3 = self.rot_ref_prev3 rot_ref_delay4 = self.rot_ref_prev4 rot_ref_delay5 = self.rot_ref_prev5 rot_ref_delay6 = self.rot_ref_prev6 rot_ref_delay7 = self.rot_ref_prev7 rot_ref_delay8 = self.rot_ref_prev8 rot_ref_delay9 = self.rot_ref_prev9 rot_ref_delay10 = self.rot_ref_prev10 rot_ref_delay11 = self.rot_ref_prev11 rot_ref_delay12 = self.rot_ref_prev12 rot_ref_delay13 = self.rot_ref_prev13 self.rot_ref_prev1 = self._reference['rot'] self.rot_ref_prev2 = rot_ref_delay1 self.rot_ref_prev3 = rot_ref_delay2 self.rot_ref_prev4 = rot_ref_delay3 self.rot_ref_prev5 = rot_ref_delay4 self.rot_ref_prev6 = rot_ref_delay5 self.rot_ref_prev7 = rot_ref_delay6 self.rot_ref_prev8 = rot_ref_delay7 self.rot_ref_prev9 = rot_ref_delay8 self.rot_ref_prev10 = rot_ref_delay9 self.rot_ref_prev11 = rot_ref_delay10 self.rot_ref_prev12 = rot_ref_delay11 self.rot_ref_prev13 = rot_ref_delay12 self.rot_ref_prev14 = rot_ref_delay13 vel_ref_delay1 = self.vel_ref_prev1 vel_ref_delay2 = self.vel_ref_prev2 vel_ref_delay3 = self.vel_ref_prev3 vel_ref_delay4 = self.vel_ref_prev4 vel_ref_delay5 = self.vel_ref_prev5 vel_ref_delay6 = self.vel_ref_prev6 vel_ref_delay7 = self.vel_ref_prev7 vel_ref_delay8 = self.vel_ref_prev8 vel_ref_delay9 = self.vel_ref_prev9 vel_ref_delay10 = self.vel_ref_prev10 vel_ref_delay11 = self.vel_ref_prev11 vel_ref_delay12 = self.vel_ref_prev12 vel_ref_delay13 = self.vel_ref_prev13 vel_ref_delay14 = self.vel_ref_prev14 self.vel_ref_prev1 = self._reference['vel'] self.vel_ref_prev2 = vel_ref_delay1 self.vel_ref_prev3 = vel_ref_delay2 self.vel_ref_prev4 = vel_ref_delay3 self.vel_ref_prev5 = vel_ref_delay4 self.vel_ref_prev6 = vel_ref_delay5 self.vel_ref_prev7 = vel_ref_delay6 self.vel_ref_prev8 = vel_ref_delay7 self.vel_ref_prev9 = vel_ref_delay8 self.vel_ref_prev10 = vel_ref_delay9 self.vel_ref_prev11 = vel_ref_delay10 self.vel_ref_prev12 = vel_ref_delay11 self.vel_ref_prev13 = vel_ref_delay12 self.vel_ref_prev14 = vel_ref_delay13 #self._reference['acc'] = np.hstack((reference.a, reference.alpha)) if reference is not None and self._reference_pub.get_num_connections( ) > 0: # Publish current reference msg = TrajectoryPointObjectFollow() msg.header.stamp = rospy.Time.now() msg.header.frame_id = self._local_planner.inertial_frame_id msg.pose.position = Vector3(*self._reference['pos']) msg.pose.orientation = Quaternion(*self._reference['rot']) msg.velocity.linear = Vector3(*self._reference['vel'][0:3]) msg.velocity.angular = Vector3(*self._reference['vel'][3:6]) #msg.acceleration.linear = Vector3(*self._reference['acc'][0:3]) #msg.acceleration.angular = Vector3(*self._reference['acc'][3:6]) self._reference_pub.publish(msg) return True def imuCallback(self, data): self._linear_acceleration = data.linear_acceleration #self.imuAccLinear[0]=self._linear_acceleration.x #self.imuAccLinear[1]=self._linear_acceleration.y #self.imuAccLinear[2]=self._linear_acceleration.z def _update_time_step(self): """Update time step.""" t = rospy.get_time() self._dt = t - self._prev_time self._prev_time = t if self._prev_time < 0.0: self._dt = 0.05 def _reset_controller(self): """Reset reference and and error vectors.""" self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) def reset_controller_callback(self, request): """Service handler function.""" self._reset_controller() return ResetControllerResponse(True) def update_controller(self): """This function must be implemented by derived classes with the implementation of the control algorithm. """ # Does nothing, must be overloaded raise NotImplementedError() def update_errors(self): """Update error vectors.""" if not self.odom_is_init: self._logger.warning('Odometry topic has not been update yet') return self._update_reference() self.count() # Calculate error in the BODY frame self._update_time_step() # Rotation matrix from INERTIAL to BODY frame rotItoB = self._vehicle_model.rotItoB rotBtoI = self._vehicle_model.rotBtoI if self._dt > 0: # Update position error with respect to the the BODY frame pos = self._vehicle_model.pos vel = self._vehicle_model.vel quat = self._vehicle_model.quat pos_veh_delay1 = self.pos_veh_prev1 pos_veh_delay2 = self.pos_veh_prev2 pos_veh_delay3 = self.pos_veh_prev3 pos_veh_delay4 = self.pos_veh_prev4 pos_veh_delay5 = self.pos_veh_prev5 pos_veh_delay6 = self.pos_veh_prev6 self.pos_veh_prev1 = self._vehicle_model.pos self.pos_veh_prev2 = pos_veh_delay1 self.pos_veh_prev3 = pos_veh_delay2 self.pos_veh_prev4 = pos_veh_delay3 self.pos_veh_prev5 = pos_veh_delay4 self.pos_veh_prev6 = pos_veh_delay5 self.pos_veh_prev7 = pos_veh_delay6 vel_veh_delay1 = self.vel_veh_prev1 vel_veh_delay2 = self.vel_veh_prev2 vel_veh_delay3 = self.vel_veh_prev3 vel_veh_delay4 = self.vel_veh_prev4 vel_veh_delay5 = self.vel_veh_prev5 vel_veh_delay6 = self.vel_veh_prev6 self.vel_veh_prev1 = self._vehicle_model.vel self.vel_veh_prev2 = vel_veh_delay1 self.vel_veh_prev3 = vel_veh_delay2 self.vel_veh_prev4 = vel_veh_delay3 self.vel_veh_prev5 = vel_veh_delay4 self.vel_veh_prev6 = vel_veh_delay5 self.vel_veh_prev7 = vel_veh_delay6 quat_veh_delay1 = self.quat_veh_prev1 quat_veh_delay2 = self.quat_veh_prev2 quat_veh_delay3 = self.quat_veh_prev3 quat_veh_delay4 = self.quat_veh_prev4 quat_veh_delay5 = self.quat_veh_prev5 quat_veh_delay6 = self.quat_veh_prev6 self.quat_veh_prev1 = self._vehicle_model.quat self.quat_veh_prev2 = quat_veh_delay1 self.quat_veh_prev3 = quat_veh_delay2 self.quat_veh_prev4 = quat_veh_delay3 self.quat_veh_prev5 = quat_veh_delay4 self.quat_veh_prev6 = quat_veh_delay5 self.quat_veh_prev7 = quat_veh_delay6 # with delay # t = rospy.get_time() # if t>3 and t<=85: # self._errors['pos'] = np.dot(rotItoB, self._reference['pos'] - self.pos_veh_prev2) # self._errors['rot'] = quaternion_multiply(quaternion_inverse(quat), self._reference['rot']) # #self._errors['vel'] = np.hstack((np.dot(rotItoB, self._reference['vel'][0:3]) - self.vel_veh_prev2[0:3],np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) # self._errors['vel'] = np.hstack((self._reference['vel'][0:3]-self.vel_veh_prev2[0:3], self._reference['vel'][3:6]-vel[3:6])) # else: # self._errors['pos'] = np.dot( # rotItoB, self._reference['pos'] - pos) # self._errors['rot'] = quaternion_multiply( # quaternion_inverse(quat), self._reference['rot']) # self._errors['vel'] = np.hstack(( # np.dot(rotItoB, self._reference['vel'][0:3]) - vel[0:3], # np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) # no delay self._errors['pos'] = np.dot(rotItoB, self._reference['pos'] - pos) self._errors['rot'] = quaternion_multiply(quaternion_inverse(quat), self._reference['rot']) self._errors['vel'] = np.hstack( (np.dot(rotItoB, self._reference['vel'][0:3]) - vel[0:3], np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) # don't touch. with delay # t = rospy.get_time() # if t>8 and t<=85: # self._errors['pos'] = np.dot(rotItoB, self._reference['pos'] - self.pos_veh_prev3) # self._errors['rot'] = quaternion_multiply(quaternion_inverse(quat), self._reference['rot']) # #self._errors['vel'] = np.hstack((np.dot(rotItoB, self._reference['vel'][0:3]) - self.vel_veh_prev2[0:3],np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) # self._errors['vel'] = np.hstack((self._reference['vel'][0:3]-self.vel_veh_prev3[0:3], self._reference['vel'][3:6]-vel[3:6])) # else: # self._errors['pos'] = np.dot( # rotItoB, self._reference['pos'] - pos) # self._errors['rot'] = quaternion_multiply( # quaternion_inverse(quat), self._reference['rot']) # self._errors['vel'] = np.hstack(( # np.dot(rotItoB, self._reference['vel'][0:3]) - vel[0:3], # np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) # no delay if self._error_pub.get_num_connections() > 0: stamp = rospy.Time.now() msg = TrajectoryPointObjectFollow() msg.header.stamp = stamp msg.header.frame_id = self._local_planner.inertial_frame_id # Publish pose error msg.pose.position = Vector3(*np.dot(rotBtoI, self._errors['pos'])) msg.pose.orientation = Quaternion(*self._errors['rot']) # Publish velocity errors in INERTIAL frame msg.velocity.linear = Vector3( *np.dot(rotBtoI, self._errors['vel'][0:3])) msg.velocity.angular = Vector3( *np.dot(rotBtoI, self._errors['vel'][3:6])) self._error_pub.publish(msg) def count(self): self._count += 1 msg = Int32() msg.data = self._count self._count_pub.publish(msg) def pub_dt(self, data): msg = Float32() msg.data = data self._dt_pub.publish(msg) def pub_dt1(self, data): msg = Float32() msg.data = data self._dt_pub1.publish(msg) def publish_MPara(self, matrixPara): msg = Matrix6() msg.x11 = matrixPara[0, 0] msg.x12 = matrixPara[0, 1] msg.x13 = matrixPara[0, 2] msg.x14 = matrixPara[0, 3] msg.x15 = matrixPara[0, 4] msg.x16 = matrixPara[0, 5] msg.x21 = matrixPara[1, 0] msg.x22 = matrixPara[1, 1] msg.x23 = matrixPara[1, 2] msg.x24 = matrixPara[1, 3] msg.x25 = matrixPara[1, 4] msg.x26 = matrixPara[1, 5] msg.x31 = matrixPara[2, 0] msg.x32 = matrixPara[2, 1] msg.x33 = matrixPara[2, 2] msg.x34 = matrixPara[2, 3] msg.x35 = matrixPara[2, 4] msg.x36 = matrixPara[2, 5] msg.x41 = matrixPara[3, 0] msg.x42 = matrixPara[3, 1] msg.x43 = matrixPara[3, 2] msg.x44 = matrixPara[3, 3] msg.x45 = matrixPara[3, 4] msg.x46 = matrixPara[3, 5] msg.x51 = matrixPara[4, 0] msg.x52 = matrixPara[4, 1] msg.x53 = matrixPara[4, 2] msg.x54 = matrixPara[4, 3] msg.x55 = matrixPara[4, 4] msg.x56 = matrixPara[4, 5] msg.x61 = matrixPara[5, 0] msg.x62 = matrixPara[5, 1] msg.x63 = matrixPara[5, 2] msg.x64 = matrixPara[5, 3] msg.x65 = matrixPara[5, 4] msg.x66 = matrixPara[5, 5] self._MParameter_pub.publish(msg) def publish_CPara(self, matrixPara): msg = Matrix6() msg.x11 = matrixPara[0, 0] msg.x12 = matrixPara[0, 1] msg.x13 = matrixPara[0, 2] msg.x14 = matrixPara[0, 3] msg.x15 = matrixPara[0, 4] msg.x16 = matrixPara[0, 5] msg.x21 = matrixPara[1, 0] msg.x22 = matrixPara[1, 1] msg.x23 = matrixPara[1, 2] msg.x24 = matrixPara[1, 3] msg.x25 = matrixPara[1, 4] msg.x26 = matrixPara[1, 5] msg.x31 = matrixPara[2, 0] msg.x32 = matrixPara[2, 1] msg.x33 = matrixPara[2, 2] msg.x34 = matrixPara[2, 3] msg.x35 = matrixPara[2, 4] msg.x36 = matrixPara[2, 5] msg.x41 = matrixPara[3, 0] msg.x42 = matrixPara[3, 1] msg.x43 = matrixPara[3, 2] msg.x44 = matrixPara[3, 3] msg.x45 = matrixPara[3, 4] msg.x46 = matrixPara[3, 5] msg.x51 = matrixPara[4, 0] msg.x52 = matrixPara[4, 1] msg.x53 = matrixPara[4, 2] msg.x54 = matrixPara[4, 3] msg.x55 = matrixPara[4, 4] msg.x56 = matrixPara[4, 5] msg.x61 = matrixPara[5, 0] msg.x62 = matrixPara[5, 1] msg.x63 = matrixPara[5, 2] msg.x64 = matrixPara[5, 3] msg.x65 = matrixPara[5, 4] msg.x66 = matrixPara[5, 5] self._CParameter_pub.publish(msg) def publish_DPara(self, matrixPara): msg = Matrix6() msg.x11 = matrixPara[0, 0] msg.x12 = matrixPara[0, 1] msg.x13 = matrixPara[0, 2] msg.x14 = matrixPara[0, 3] msg.x15 = matrixPara[0, 4] msg.x16 = matrixPara[0, 5] msg.x21 = matrixPara[1, 0] msg.x22 = matrixPara[1, 1] msg.x23 = matrixPara[1, 2] msg.x24 = matrixPara[1, 3] msg.x25 = matrixPara[1, 4] msg.x26 = matrixPara[1, 5] msg.x31 = matrixPara[2, 0] msg.x32 = matrixPara[2, 1] msg.x33 = matrixPara[2, 2] msg.x34 = matrixPara[2, 3] msg.x35 = matrixPara[2, 4] msg.x36 = matrixPara[2, 5] msg.x41 = matrixPara[3, 0] msg.x42 = matrixPara[3, 1] msg.x43 = matrixPara[3, 2] msg.x44 = matrixPara[3, 3] msg.x45 = matrixPara[3, 4] msg.x46 = matrixPara[3, 5] msg.x51 = matrixPara[4, 0] msg.x52 = matrixPara[4, 1] msg.x53 = matrixPara[4, 2] msg.x54 = matrixPara[4, 3] msg.x55 = matrixPara[4, 4] msg.x56 = matrixPara[4, 5] msg.x61 = matrixPara[5, 0] msg.x62 = matrixPara[5, 1] msg.x63 = matrixPara[5, 2] msg.x64 = matrixPara[5, 3] msg.x65 = matrixPara[5, 4] msg.x66 = matrixPara[5, 5] self._DParameter_pub.publish(msg) def publish_vel(self, vel): msg = Vector6() msg.x1 = vel[0] msg.x2 = vel[1] msg.x3 = vel[2] msg.x4 = vel[3] msg.x5 = vel[4] msg.x6 = vel[5] self._vel_pub.publish(msg) def publish_restoring(self, sur): msg = Vector6() msg.x1 = sur[0] msg.x2 = sur[1] msg.x3 = sur[2] msg.x4 = sur[3] msg.x5 = sur[4] msg.x6 = sur[5] self._restoring_pub.publish(msg) def publish_generalForce(self, sur): msg = Vector6() msg.x1 = sur[0] msg.x2 = sur[1] msg.x3 = sur[2] msg.x4 = sur[3] msg.x5 = sur[4] msg.x6 = sur[5] self._generalForce_pub.publish(msg) def publish_equivalentControl(self, sur): msg = Vector6() msg.x1 = sur[0] msg.x2 = sur[1] msg.x3 = sur[2] msg.x4 = sur[3] msg.x5 = sur[4] msg.x6 = sur[5] self._equivalentControl_pub.publish(msg) def publish_slidingSurface(self, slidingSurface): force_msg = WrenchStamped() force_msg.header.stamp = rospy.Time.now() force_msg.header.frame_id = '%s/%s' % ( self._namespace, self._vehicle_model.body_frame_id) force_msg.wrench.force.x = slidingSurface[0] force_msg.wrench.force.y = slidingSurface[1] force_msg.wrench.force.z = slidingSurface[2] force_msg.wrench.torque.x = slidingSurface[3] force_msg.wrench.torque.y = slidingSurface[4] force_msg.wrench.torque.z = slidingSurface[5] self._sliding_pub.publish(force_msg) def publish_control_wrench(self, force): """Publish the thruster manager control set-point. > *Input arguments* * `force` (*type:* `numpy.array`): 6 DoF control set-point wrench vector """ if not self.odom_is_init: return # Apply saturation for i in range(6): if force[i] < -self._control_saturation: force[i] = -self._control_saturation elif force[i] > self._control_saturation: force[i] = self._control_saturation if not self.thrusters_only: surge_speed = self._vehicle_model.vel[0] self.publish_auv_command(surge_speed, force) return force_msg = WrenchStamped() force_msg.header.stamp = rospy.Time.now() force_msg.header.frame_id = '%s/%s' % ( self._namespace, self._vehicle_model.body_frame_id) force_msg.wrench.force.x = force[0] force_msg.wrench.force.y = force[1] force_msg.wrench.force.z = force[2] force_msg.wrench.torque.x = force[3] force_msg.wrench.torque.y = force[4] force_msg.wrench.torque.z = force[5] self._thrust_pub.publish(force_msg) def publish_auv_command(self, surge_speed, wrench): """Publish the AUV control command message > *Input arguments* * `surge_speed` (*type:* `float`): Reference surge speed * `wrench` (*type:* `numpy.array`): 6 DoF wrench vector """ if not self.odom_is_init: return surge_speed = max(0, surge_speed) msg = AUVCommand() msg.header.stamp = rospy.Time.now() msg.header.frame_id = '%s/%s' % (self._namespace, self._vehicle_model.body_frame_id) msg.surge_speed = surge_speed msg.command.force.x = max(self._min_thrust, wrench[0]) msg.command.force.y = wrench[1] msg.command.force.z = wrench[2] msg.command.torque.x = wrench[3] msg.command.torque.y = wrench[4] msg.command.torque.z = wrench[5] self._auv_command_pub.publish(msg) def _odometry_callback(self, msg): """Odometry topic subscriber callback function. > *Input arguments* * `msg` (*type:* `nav_msgs/Odometry`): Input odometry message """ self._vehicle_model.update_odometry(msg) if not self._init_odom: self._init_odom = True if len(self._odometry_callbacks): for func in self._odometry_callbacks: func()
class DPControllerBase(object): """General abstract class for DP controllers for underwater vehicles. This is an abstract class, must be inherited by a controller module that overrides the update_controller method. If the controller is set to be model based (is_model_based=True), than the vehicle parameters are going to be read from the ROS parameter server. """ _LABEL = '' def __init__(self, is_model_based=False, list_odometry_callbacks=[], planner_full_dof=False): # Flag will be set to true when all parameters are initialized correctly self._is_init = False self._logger = logging.getLogger('dp_controller') out_hdlr = logging.StreamHandler(sys.stdout) out_hdlr.setFormatter(logging.Formatter( rospy.get_namespace().replace('/', '').upper() + ' -- %(asctime)s | %(levelname)s | %(module)s | %(message)s')) out_hdlr.setLevel(logging.INFO) self._logger.addHandler(out_hdlr) self._logger.setLevel(logging.INFO) # Reading current namespace self._namespace = rospy.get_namespace() # Configuration for the vehicle dynamic model self._is_model_based = is_model_based if self._is_model_based: self._logger.info('Setting controller as model-based') else: self._logger.info('Setting controller as non-model-based') self._use_stamped_poses_only = False if rospy.has_param('~use_stamped_poses_only'): self._use_stamped_poses_only = rospy.get_param('~use_stamped_poses_only') # Flag indicating if the vehicle has only thrusters, otherwise # the AUV allocation node will be used self.thrusters_only = rospy.get_param('~thrusters_only', True) # Instance of the local planner for local trajectory generation self._local_planner = LocalPlanner( full_dof=planner_full_dof, stamped_pose_only=self._use_stamped_poses_only, thrusters_only=self.thrusters_only) self._control_saturation = 5000 # TODO: Fix the saturation term and how it is applied if rospy.has_param('~saturation'): self._thrust_saturation = rospy.get_param('~saturation') if self._control_saturation <= 0: raise rospy.ROSException('Invalid control saturation forces') # Flag indicating either use of the AUV control allocator or # direct command of fins and thruster self.use_auv_control_allocator = False if not self.thrusters_only: self.use_auv_control_allocator = rospy.get_param('~use_auv_control_allocator', False) # Remap the following topics, if needed # Publisher for thruster allocator if self.thrusters_only: self._thrust_pub = rospy.Publisher( 'thruster_output', WrenchStamped, queue_size=1) else: self._thrust_pub = None if not self.thrusters_only: self._auv_command_pub = rospy.Publisher( 'auv_command_output', AUVCommand, queue_size=1) else: self._auv_command_pub = None self._min_thrust = rospy.get_param('~min_thrust', 40.0) self._reference_pub = rospy.Publisher('reference', TrajectoryPoint, queue_size=1) # Publish error (for debugging) self._error_pub = rospy.Publisher('error', TrajectoryPoint, queue_size=1) self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6), acc=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) # Time step self._dt = 0 self._prev_time = rospy.get_time() self._services = dict() self._services['reset'] = rospy.Service('reset_controller', ResetController, self.reset_controller_callback) # Time stamp for the received trajectory self._stamp_trajectory_received = rospy.get_time() # Instance of the vehicle model self._vehicle_model = None # If list of callbacks is empty, set the default if len(list_odometry_callbacks): self._odometry_callbacks = list_odometry_callbacks else: self._odometry_callbacks = [self.update_errors, self.update_controller] # Initialize vehicle, if model based self._create_vehicle_model() # Flag to indicate that odometry topic is receiving data self._init_odom = False # Subscribe to odometry topic self._odom_topic_sub = rospy.Subscriber( 'odom', numpy_msg(Odometry), self._odometry_callback) # Stores last simulation time self._prev_t = -1.0 self._logger.info('DP controller successfully initialized') def __del__(self): # Removing logging message handlers while self._logger.handlers: self._logger.handlers.pop() @staticmethod def get_controller(name, *args): """Create instance of a specific DP controller.""" for controller in DPControllerBase.__subclasses__(): if name == controller.__name__: print 'Creating controller=', name return controller(*args) @staticmethod def get_list_of_controllers(): """Return list of DP controllers using this interface.""" return [controller.__name__ for controller in DPControllerBase.__subclasses__()] @property def label(self): return self._LABEL @property def odom_is_init(self): return self._init_odom @property def error_pos_world(self): return np.dot(self._vehicle_model.rotBtoI, self._errors['pos']) @property def error_orientation_quat(self): return deepcopy(self._errors['rot'][0:3]) @property def error_orientation_rpy(self): """Return orientation error in Euler angles.""" e1 = self._errors['rot'][0] e2 = self._errors['rot'][1] e3 = self._errors['rot'][2] eta = self._errors['rot'][3] rot = np.array([[1 - 2 * (e2**2 + e3**2), 2 * (e1 * e2 - e3 * eta), 2 * (e1 * e3 + e2 * eta)], [2 * (e1 * e2 + e3 * eta), 1 - 2 * (e1**2 + e3**2), 2 * (e2 * e3 - e1 * eta)], [2 * (e1 * e3 - e2 * eta), 2 * (e2 * e3 + e1 * eta), 1 - 2 * (e1**2 + e2**2)]]) # Roll roll = np.arctan2(rot[2, 1], rot[2, 2]) # Pitch, treating singularity cases den = np.sqrt(1 - rot[2, 1]**2) pitch = - np.arctan(rot[2, 1] / max(0.001, den)) # Yaw yaw = np.arctan2(rot[1, 0], rot[0, 0]) return np.array([roll, pitch, yaw]) @property def error_pose_euler(self): """Pose error with orientation represented in Euler angles.""" return np.hstack((self._errors['pos'], self.error_orientation_rpy)) @property def error_vel_world(self): return np.dot(self._vehicle_model.rotBtoI, self._errors['vel']) def __str__(self): msg = 'Dynamic positioning controller\n' msg += 'Controller= ' + self._LABEL + '\n' msg += 'Is model based? ' + str(self._is_model_based) + '\n' msg += 'Vehicle namespace= ' + self._namespace return msg def _create_vehicle_model(self): """ Create a new instance of a vehicle model. If controller is not model based, this model will have its parameters set to 0 and will be used to receive and transform the odometry data. """ if self._vehicle_model is not None: del self._vehicle_model self._vehicle_model = Vehicle( inertial_frame_id=self._local_planner.inertial_frame_id) def _update_reference(self): # Update the local planner's information about the vehicle's pose self._local_planner.update_vehicle_pose( self._vehicle_model.pos, self._vehicle_model.quat) t = rospy.get_time() reference = self._local_planner.interpolate(t) if reference is not None: self._reference['pos'] = reference.p self._reference['rot'] = reference.q self._reference['vel'] = np.hstack((reference.v, reference.w)) self._reference['acc'] = np.hstack((reference.a, reference.alpha)) # print '------------------ REFERENCE\n' # print reference if reference is not None and self._reference_pub.get_num_connections() > 0: # Publish current reference msg = TrajectoryPoint() msg.header.stamp = rospy.Time.now() msg.header.frame_id = self._local_planner.inertial_frame_id msg.pose.position = Vector3(*self._reference['pos']) msg.pose.orientation = Quaternion(*self._reference['rot']) msg.velocity.linear = Vector3(*self._reference['vel'][0:3]) msg.velocity.angular = Vector3(*self._reference['vel'][3:6]) msg.acceleration.linear = Vector3(*self._reference['acc'][0:3]) msg.acceleration.angular = Vector3(*self._reference['acc'][3:6]) self._reference_pub.publish(msg) return True def _update_time_step(self): t = rospy.get_time() self._dt = t - self._prev_time self._prev_time = t def _reset_controller(self): self._init_reference = False # Reference with relation to the INERTIAL frame self._reference = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6), acc=np.zeros(6)) # Errors wih relation to the BODY frame self._errors = dict(pos=np.zeros(3), rot=np.zeros(4), vel=np.zeros(6)) def reset_controller_callback(self, request): self._reset_controller() return ResetControllerResponse(True) def update_controller(self): # Does nothing, must be overloaded raise NotImplementedError() def update_errors(self): if not self.odom_is_init: self._logger.warning('Odometry topic has not been update yet') return self._update_reference() # Calculate error in the BODY frame self._update_time_step() # Rotation matrix from INERTIAL to BODY frame rotItoB = self._vehicle_model.rotItoB rotBtoI = self._vehicle_model.rotBtoI if self._dt > 0: # Update position error with respect to the the BODY frame pos = self._vehicle_model.pos vel = self._vehicle_model.vel quat = self._vehicle_model.quat self._errors['pos'] = np.dot( rotItoB, self._reference['pos'] - pos) # Update orientation error self._errors['rot'] = quaternion_multiply( quaternion_inverse(quat), self._reference['rot']) # Velocity error with respect to the the BODY frame self._errors['vel'] = np.hstack(( np.dot(rotItoB, self._reference['vel'][0:3]) - vel[0:3], np.dot(rotItoB, self._reference['vel'][3:6]) - vel[3:6])) if self._error_pub.get_num_connections() > 0: stamp = rospy.Time.now() msg = TrajectoryPoint() msg.header.stamp = stamp msg.header.frame_id = self._local_planner.inertial_frame_id # Publish pose error msg.pose.position = Vector3(*np.dot(rotBtoI, self._errors['pos'])) msg.pose.orientation = Quaternion(*self._errors['rot']) # Publish velocity errors in INERTIAL frame msg.velocity.linear = Vector3(*np.dot(rotBtoI, self._errors['vel'][0:3])) msg.velocity.angular = Vector3(*np.dot(rotBtoI, self._errors['vel'][3:6])) self._error_pub.publish(msg) def publish_control_wrench(self, force): if not self.odom_is_init: return # Apply saturation for i in range(6): if force[i] < -self._control_saturation: force[i] = -self._control_saturation elif force[i] > self._control_saturation: force[i] = self._control_saturation if not self.thrusters_only: surge_speed = self._vehicle_model.vel[0] self.publish_auv_command(surge_speed, force) return force_msg = WrenchStamped() force_msg.header.stamp = rospy.Time.now() force_msg.header.frame_id = '%s/%s' % (self._namespace, self._vehicle_model.body_frame_id) force_msg.wrench.force.x = force[0] force_msg.wrench.force.y = force[1] force_msg.wrench.force.z = force[2] force_msg.wrench.torque.x = force[3] force_msg.wrench.torque.y = force[4] force_msg.wrench.torque.z = force[5] self._thrust_pub.publish(force_msg) def publish_auv_command(self, surge_speed, wrench): if not self.odom_is_init: return surge_speed = max(0, surge_speed) msg = AUVCommand() msg.header.stamp = rospy.Time.now() msg.header.frame_id = '%s/%s' % (self._namespace, self._vehicle_model.body_frame_id) msg.surge_speed = surge_speed msg.command.force.x = max(self._min_thrust, wrench[0]) msg.command.force.y = wrench[1] msg.command.force.z = wrench[2] msg.command.torque.x = wrench[3] msg.command.torque.y = wrench[4] msg.command.torque.z = wrench[5] self._auv_command_pub.publish(msg) def _odometry_callback(self, msg): """Odometry topic subscriber callback function.""" self._vehicle_model.update_odometry(msg) if not self._init_odom: self._init_odom = True if len(self._odometry_callbacks): for func in self._odometry_callbacks: func()