tutorial_dp_controller.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Copyright (c) 2016 The UUV Simulator Authors.
3 # All rights reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 # Import the ROS Python library
18 import rospy
19 # Import the NumPy package for numerical computations
20 import numpy as np
21 # Import the dynamic positioning controller base class to inherit methods such as error computation update,
22 # publishing from some important ROS topics (e.g. trajectory, pose and velocity reference) and access to
23 # the vehicle model class, that is necessary to explicitly use the vehicle model
24 from uuv_control_interfaces import DPControllerBase
25 
26 
27 class TutorialDPController(DPControllerBase):
28  # A new controller that is based on the DPControllerBase must at least provide the implementation of
29  # the method update_controller.
30  # The _reset_controller method can also be overridden and it will be called every time there is a service call
31  # <vehicle namespace>/reset_controller. The default implementation sets the reference and error vectors to
32  # zero.
33  # The update_controller method must contain the implementation of the control algorithm and will be called
34  # by every update of the vehicle's odometry message. It is therefore not necessary to explicitly call this update
35  # function in this controller implementation.
36  # For the controller to send the control torques to the vehicle's thruster manager, at the end of the
37  # update_controller function the 6 x 1 control vector (type numpy.ndarray) sent using the function
38  # publish_control_wrench from the super class, which will generate a Wrench ROS message and publish it to the
39  # correspondent thruster manager node.
40  # For this tutorial, a simple PID controller will be implemented. The controller's control torque output is
41  # "tau" therefore computed as:
42  #
43  # tau = Kp * e + Kd * de/dt + Ki int_e
44  #
45  # where e is the pose error vector, in this case defined as e = (x, y, z, roll, pitch, yaw)^T
46 
47  def __init__(self):
48  # Calling the constructor of the super-class DPControllerBase, which has the implementation of the error
49  # computation update and to publish the resulting torque control vector.
50  super(TutorialDPController, self).__init__(self)
51 
52  # The controller should read its parameters from the ROS parameter server for initial setup
53  # One way to do this is to read the parameters from the node's private parameter namespace, which is done by
54  # reading the parameter tag with an "~" at the beginning. If this method is used, the parameters should be
55  # initialized accordingly in the controller startup launch file, such as
56  #
57  # <launch>
58  # <node pkg="example_package" type="example_node.py" name="example_node" output="screen">
59  # <rosparam>
60  # param_1: 0.0
61  # param_2: 0.0
62  # </rosparam>
63  # </node>
64  # </launch>
65  #
66  # For more information, see http://wiki.ros.org/roscpp_tutorials/Tutorials/AccessingPrivateNamesWithNodeHandle
67 
68  # Let's initialize the controller gain matrices Kp, Kd and Ki
69  self._Kp = np.zeros(shape=(6, 6))
70  self._Kd = np.zeros(shape=(6, 6))
71  self._Ki = np.zeros(shape=(6, 6))
72  # Initialize the integrator component
73  self._int = np.zeros(shape=(6,))
74  # Initialize variable that will store the vehicle pose error
75  self._error_pose = np.zeros(shape=(6,))
76 
77  # Now the gain matrices need to be set according to the variables stored in the parameter server
78  # For simplicity, the gain matrices are defined as diagonal matrices, so only 6 coefficients are
79  # needed
80  if rospy.get_param('~Kp'):
81  Kp_diag = rospy.get_param('~Kp')
82  if len(Kp_diag) == 6:
83  self._Kp = np.diag(Kp_diag)
84  else:
85  # If the vector provided has the wrong dimension, raise an exception
86  raise rospy.ROSException('For the Kp diagonal matrix, 6 coefficients are needed')
87 
88  # Do the same for the other two matrices
89  if rospy.get_param('~Kd'):
90  diag = rospy.get_param('~Kd')
91  if len(diag) == 6:
92  self._Kd = np.diag(diag)
93  print 'Kd=\n', self._Kd
94  else:
95  # If the vector provided has the wrong dimension, raise an exception
96  raise rospy.ROSException('For the Kd diagonal matrix, 6 coefficients are needed')
97 
98  if rospy.get_param('~Ki'):
99  diag = rospy.get_param('~Ki')
100  if len(diag) == 6:
101  self._Ki = np.diag(diag)
102  print 'Ki=\n', self._Ki
103  else:
104  # If the vector provided has the wrong dimension, raise an exception
105  raise rospy.ROSException('For the Ki diagonal matrix, 6 coefficients are needed')
106  self._is_init = True
107 
108  def _reset_controller(self):
109  # The _reset_controller method from the super class DPControllerBase already sets the error
110  # and reference vectors to zero, but this class has additional attributes that should also
111  # be taken care of.
112  # This implementation will, therefore, first call the super class reset method
113  super(TutorialDPController, self)._reset_controller()
114  # And then proceed to set the internal variables back to zero
115  self._error_pose = np.zeros(shape=(6,))
116  self._int = np.zeros(shape=(6,))
117 
118  def update_controller(self):
119  if not self._is_init:
120  return False
121  # The controller algorithm must be implemented here, the super class will connect this method
122  # to the odometry update as a callback function
123 
124  # First test whether or not the odometry topic subscriber has already been initialized
125  if not self.odom_is_init:
126  return
127 
128  # Update the integrator, read the super class vector for the pose error (orientation is represented
129  # with Euler angles in RPY convention) and integrate to the stored pose error from the last
130  # iteration
131  self._int = self._int + 0.5 * (self.error_pose_euler + self._error_pose) * self._dt
132 
133  # Store the current pose error for the next iteration
134  self._error_pose = self.error_pose_euler
135 
136  # Compute the control forces and torques using the current error vectors available
137  tau = np.dot(self._Kp, self.error_pose_euler) + np.dot(self._Kd, self._errors['vel']) + \
138  np.dot(self._Ki, self._int)
139 
140  # Use the super class method to convert the control force vector into a ROS message
141  # and publish it as an input to the vehicle's thruster manager. The thruster manager module
142  # will then distribute the efforts amongst the thrusters using the thruster allocation matrix
143  self.publish_control_wrench(tau)
144 
145 if __name__ == '__main__':
146  # Since this is an ROS node, this Python script has to be treated as an executable
147  # Remember to convert this Python file into an executable. This can be done with
148  #
149  # cd <path_to_ros_package>/scripts
150  # chmod 777 tutorial_dp_controller.py
151  #
152  # This file has also to be included in this package's CMakeLists.txt
153  # After the line catkin_package() in the CMakeLists.txt, include the following
154  #
155  # catkin_install_python(PROGRAMS scripts/tutorial_dp_controller.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
156 
157  print('Tutorial - DP Controller')
158  rospy.init_node('tutorial_dp_controller')
159 
160  try:
162  rospy.spin()
163  except rospy.ROSInterruptException:
164  print('caught exception')
165  print('exiting')


uuv_tutorial_dp_controller
Author(s):
autogenerated on Mon Jul 1 2019 19:39:38