19 from tf_quaternion
import transformations
21 from os.path
import isdir, join
23 from time
import sleep
24 from uuv_gazebo_ros_plugins_msgs.msg
import FloatStamped
25 from geometry_msgs.msg
import Wrench
26 import xml.etree.ElementTree
as etree
28 from .models
import Thruster
33 The thruster manager generates the thruster allocation matrix using the 34 TF information and publishes the thruster forces assuming the the thruster 35 topics are named in the following pattern 37 <thruster_topic_prefix>/<index>/<thruster_topic_suffix> 39 Thruster frames should also be named as follows 41 <thruster_frame_base>_<index> 47 """Class constructor.""" 61 if not rospy.has_param(
'thruster_manager'):
62 raise rospy.ROSException(
'Thruster manager parameters not ' 63 'initialized for uuv_name=' +
67 self.
config = rospy.get_param(
'thruster_manager')
69 robot_description_param = self.
namespace +
'robot_description' 72 if rospy.has_param(robot_description_param):
74 self.
parse_urdf(rospy.get_param(robot_description_param))
76 if self.
config[
'update_rate'] < 0:
77 self.
config[
'update_rate'] = 50
83 tf_trans_ned_to_enu =
None 87 target =
'{}base_link'.format(self.
namespace)
89 source =
'{}base_link_ned'.format(self.
namespace)
92 source =
'base_link_ned' 94 tf_trans_ned_to_enu = tf_buffer.lookup_transform(
95 target, source, rospy.Time(), rospy.Duration(1))
96 except Exception
as e:
97 rospy.loginfo(
'No transform found between base_link and base_link_ned' 98 ' for vehicle {}, message={}'.format(self.
namespace, e))
101 if tf_trans_ned_to_enu
is not None:
103 (tf_trans_ned_to_enu.transform.rotation.x,
104 tf_trans_ned_to_enu.transform.rotation.y,
105 tf_trans_ned_to_enu.transform.rotation.z,
106 tf_trans_ned_to_enu.transform.rotation.w))[0:3, 0:3]
111 'ThrusterManager::update_rate=' + str(self.
config[
'update_rate']))
114 rospy.set_param(
'thruster_manager/tf_prefix', self.
namespace)
119 if rospy.has_param(
'~output_dir'):
120 self.
output_dir = rospy.get_param(
'~output_dir')
122 raise rospy.ROSException(
123 'Invalid output directory, output_dir=' + self.
output_dir)
124 rospy.loginfo(
'output_dir=' + self.
output_dir)
137 if rospy.has_param(
'~tam'):
138 tam = rospy.get_param(
'~tam')
141 self.
n_thrusters = self.configuration_matrix.shape[1]
143 params = self.
config[
'conversion_fcn_params']
144 conv_fcn = self.
config[
'conversion_fcn']
145 if type(params) == list
and type(conv_fcn) == list:
147 raise rospy.ROSException(
'Lists conversion_fcn and ' 148 'conversion_fcn_params must have ' 149 'the same number of items as of ' 152 topic = self.
config[
'thruster_topic_prefix'] + str(i) + \
153 self.
config[
'thruster_topic_suffix']
154 if list
not in [type(params), type(conv_fcn)]:
155 thruster = Thruster.create_thruster(
156 conv_fcn, i, topic,
None,
None,
159 thruster = Thruster.create_thruster(
160 conv_fcn[i], i, topic,
None,
None,
164 rospy.ROSException(
'Invalid thruster conversion ' 166 % self.
config[
'conversion_fcn'])
167 self.thrusters.append(thruster)
168 rospy.loginfo(
'Thruster allocation matrix provided!')
169 rospy.loginfo(
'TAM=')
174 raise rospy.ROSException(
'No thrusters found')
184 with open(join(self.
output_dir,
'TAM.yaml'),
'w')
as yaml_file:
187 dict(tam=self.configuration_matrix.tolist())))
189 rospy.loginfo(
'Invalid output directory for the TAM matrix, dir=' + str(self.
output_dir))
192 rospy.loginfo(
'ThrusterManager: ready')
195 root = etree.fromstring(urdf_str)
196 for joint
in root.findall(
'joint'):
197 if joint.get(
'type') ==
'fixed':
199 axis_str_list = joint.find(
'axis').get(
'xyz').split()
200 child = joint.find(
'child').get(
'link')
204 self.
axes[child] = numpy.array([float(axis_str_list[0]),
205 float(axis_str_list[1]),
206 float(axis_str_list[2]), 0.0])
210 """Calculate the thruster allocation matrix, if one is not given.""" 213 rospy.loginfo(
'TAM provided, skipping...')
214 rospy.loginfo(
'ThrusterManager: ready')
218 rospy.loginfo(
'ThrusterManager: updating thruster poses')
220 now = rospy.Time.now() + rospy.Duration(0.2)
226 equal_thrusters =
True 227 idx_thruster_model = 0
229 if type(self.
config[
'conversion_fcn_params']) == list
and \
230 type(self.
config[
'conversion_fcn']) == list:
231 if len(self.
config[
'conversion_fcn_params']) != len(
232 self.
config[
'conversion_fcn']):
233 raise rospy.ROSException(
234 'Lists of conversion_fcn_params and conversion_fcn' 235 ' must have equal length')
236 equal_thrusters =
False 238 rospy.loginfo(
'conversion_fcn=' + str(self.
config[
'conversion_fcn']))
239 rospy.loginfo(
'conversion_fcn_params=' + str(self.
config[
'conversion_fcn_params']))
246 self.
config[
'thruster_frame_base'] + str(i)
249 rospy.loginfo(
'transform: ' + base +
' -> ' + frame)
250 now = rospy.Time.now() + rospy.Duration(0.2)
251 listener.waitForTransform(base, frame,
252 now, rospy.Duration(1.0))
253 [pos, quat] = listener.lookupTransform(base, frame, now)
255 topic = self.
config[
'thruster_topic_prefix'] + str(i) + \
256 self.
config[
'thruster_topic_suffix']
263 params = self.
config[
'conversion_fcn_params']
264 thruster = Thruster.create_thruster(
265 self.
config[
'conversion_fcn'],
266 i, topic, pos, quat, self.
axes[frame], **params)
268 if idx_thruster_model >= len(self.
config[
'conversion_fcn']):
269 raise rospy.ROSException(
'Number of thrusters found and ' 270 'conversion_fcn are different')
271 params = self.
config[
'conversion_fcn_params'][idx_thruster_model]
272 conv_fcn = self.
config[
'conversion_fcn'][idx_thruster_model]
273 thruster = Thruster.create_thruster(
275 i, topic, pos, quat, self.
axes[frame],
277 idx_thruster_model += 1
279 rospy.ROSException(
'Invalid thruster conversion ' 281 % self.
config[
'conversion_fcn'])
282 self.thrusters.append(thruster)
284 rospy.loginfo(
'could not get transform from: ' + base)
285 rospy.loginfo(
'to: ' + frame)
316 if self.
output_dir is not None and not recalculate:
317 with open(join(self.
output_dir,
'TAM.yaml'),
'w')
as yaml_file:
320 dict(tam=self.configuration_matrix.tolist())))
321 rospy.loginfo(
'TAM saved in <{}>'.format(join(self.
output_dir,
'TAM.yaml')))
323 rospy.loginfo(
'Recalculate flag on, matrix will not be stored in TAM.yaml')
325 rospy.logerr(
'Invalid output directory for the TAM matrix, dir='.format(
329 rospy.loginfo(
'ThrusterManager: ready')
333 """Publish the thruster input into their specific topic.""" 344 if frame_id
is not None:
345 if self.
config[
'base_link'] != frame_id:
347 ' base_link_ned to base_link could not be found' 348 if 'base_link_ned' not in self.
config[
'base_link']:
354 control_forces = numpy.dot(self.base_link_ned_to_enu.T,
356 control_torques = numpy.dot(self.base_link_ned_to_enu.T,
359 gen_forces = numpy.hstack(
360 (control_forces, control_torques)).transpose()
365 """Compute desired thruster forces using the inverse configuration 369 thrust = self.inverse_configuration_matrix.dot(gen_forces)
372 limitation_factor = 1.0
373 if type(self.
config[
'max_thrust']) == list:
375 raise rospy.ROSException(
'max_thrust list must have the length' 376 ' equal to the number of thrusters')
377 max_thrust = self.
config[
'max_thrust']
381 if abs(thrust[i]) > max_thrust[i]:
382 thrust[i] = numpy.sign(thrust[i]) * max_thrust[i]
def publish_thrust_forces(self, control_forces, control_torques, frame_id=None)
def command_thrusters(self)
def parse_urdf(self, urdf_str)
def update_tam(self, recalculate=False)
def compute_thruster_forces(self, gen_forces)
inverse_configuration_matrix