usv_gazebo_thrust_plugin.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2017 Brian Bingham
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16 */
17 
18 #include <boost/algorithm/clamp.hpp>
19 #include <ros/time.h>
20 
21 #include <cmath>
22 #include <functional>
23 
25 
26 using namespace gazebo;
27 
30 {
31  // Initialize fields
32  this->plugin = _parent;
33  this->engineJointPID.Init(300, 0.0, 20);
34  this->currCmd = 0.0;
35  this->desiredAngle = 0.0;
36 
37  #if GAZEBO_MAJOR_VERSION >= 8
38  this->lastCmdTime = this->plugin->world->SimTime();
39  #else
40  this->lastCmdTime = this->plugin->world->GetSimTime();
41  #endif
42 }
43 
45 void Thruster::OnThrustCmd(const std_msgs::Float32::ConstPtr &_msg)
46 {
47  // When we get a new thrust command!
48  ROS_DEBUG_STREAM("New thrust command! " << _msg->data);
49  std::lock_guard<std::mutex> lock(this->plugin->mutex);
50  #if GAZEBO_MAJOR_VERSION >= 8
51  this->lastCmdTime = this->plugin->world->SimTime();
52  #else
53  this->lastCmdTime = this->plugin->world->GetSimTime();
54  #endif
55  this->currCmd = _msg->data;
56 }
57 
59 void Thruster::OnThrustAngle(const std_msgs::Float32::ConstPtr &_msg)
60 {
61  // When we get a new thrust angle!
62  ROS_DEBUG_STREAM("New thrust angle! " << _msg->data);
63  std::lock_guard<std::mutex> lock(this->plugin->mutex);
64  this->desiredAngle = boost::algorithm::clamp(_msg->data, -this->maxAngle,
65  this->maxAngle);
66 }
67 
69 double UsvThrust::SdfParamDouble(sdf::ElementPtr _sdfPtr,
70  const std::string &_paramName, const double _defaultVal) const
71 {
72  if (!_sdfPtr->HasElement(_paramName))
73  {
74  ROS_INFO_STREAM("Parameter <" << _paramName << "> not found: "
75  "Using default value of <" << _defaultVal << ">.");
76  return _defaultVal;
77  }
78 
79  double val = _sdfPtr->Get<double>(_paramName);
80  ROS_DEBUG_STREAM("Parameter found - setting <" << _paramName <<
81  "> to <" << val << ">.");
82  return val;
83 }
84 
86 void UsvThrust::Load(physics::ModelPtr _parent, sdf::ElementPtr _sdf)
87 {
88  ROS_DEBUG("Loading usv_gazebo_thrust_plugin");
89  this->model = _parent;
90  this->world = this->model->GetWorld();
91 
92  // Get parameters from SDF
93  std::string nodeNamespace = "";
94  if (_sdf->HasElement("robotNamespace"))
95  {
96  nodeNamespace = _sdf->Get<std::string>("robotNamespace") + "/";
97  ROS_INFO_STREAM("Thruster namespace <" << nodeNamespace << ">");
98  }
99 
100  this->cmdTimeout = this->SdfParamDouble(_sdf, "cmdTimeout", 1.0);
101 
102  ROS_DEBUG_STREAM("Loading thrusters from SDF");
103 
104  // For each thruster
105  int thrusterCounter = 0;
106  if (_sdf->HasElement("thruster"))
107  {
108  sdf::ElementPtr thrusterSDF = _sdf->GetElement("thruster");
109  while (thrusterSDF)
110  {
111  // Instatiate
112  Thruster thruster(this);
113 
114  ROS_DEBUG_STREAM("Thruster #" << thrusterCounter);
115 
116  // REQUIRED PARAMETERS
117  // Find link by name in SDF
118  if (thrusterSDF->HasElement("linkName"))
119  {
120  std::string linkName = thrusterSDF->Get<std::string>("linkName");
121  thruster.link = this->model->GetLink(linkName);
122  if (!thruster.link)
123  {
124  ROS_ERROR_STREAM("Could not find a link by the name <" << linkName
125  << "> in the model!");
126  }
127  else
128  {
129  ROS_DEBUG_STREAM("Thruster added to link <" << linkName << ">");
130  }
131  }
132  else
133  {
134  ROS_ERROR_STREAM("Please specify a link name for each thruster!");
135  }
136 
137  // Parse out propellor joint name
138  if (thrusterSDF->HasElement("propJointName"))
139  {
140  std::string propName =
141  thrusterSDF->GetElement("propJointName")->Get<std::string>();
142  thruster.propJoint = this->model->GetJoint(propName);
143  if (!thruster.propJoint)
144  {
145  ROS_ERROR_STREAM("Could not find a propellor joint by the name of <"
146  << propName << "> in the model!");
147  }
148  else
149  {
150  ROS_DEBUG_STREAM("Propellor joint <" << propName <<
151  "> added to thruster");
152  }
153  }
154  else
155  {
156  ROS_ERROR_STREAM("No propJointName SDF parameter for thruster #"
157  << thrusterCounter);
158  }
159 
160  // Parse out engine joint name
161  if (thrusterSDF->HasElement("engineJointName"))
162  {
163  std::string engineName =
164  thrusterSDF->GetElement("engineJointName")->Get<std::string>();
165  thruster.engineJoint = this->model->GetJoint(engineName);
166  if (!thruster.engineJoint)
167  {
168  ROS_ERROR_STREAM("Could not find a engine joint by the name of <" <<
169  engineName << "> in the model!");
170  }
171  else
172  {
173  ROS_DEBUG_STREAM("Engine joint <" << engineName <<
174  "> added to thruster");
175  }
176  }
177  else
178  {
179  ROS_ERROR_STREAM("No engineJointName SDF parameter for thruster #"
180  << thrusterCounter);
181  }
182 
183  // Parse for cmd subscription topic
184  if (thrusterSDF->HasElement("cmdTopic"))
185  {
186  thruster.cmdTopic = thrusterSDF->Get<std::string>("cmdTopic");
187  }
188  else
189  {
190  ROS_ERROR_STREAM("Please specify a cmdTopic (for ROS subscription) "
191  "for each thruster!");
192  }
193 
194  // Parse for angle subscription topic
195  if (thrusterSDF->HasElement("angleTopic"))
196  {
197  thruster.angleTopic = thrusterSDF->Get<std::string>("angleTopic");
198  }
199  else
200  {
201  ROS_ERROR_STREAM("Please specify a angleTopic (for ROS subscription) "
202  "for each thruster!");
203  }
204 
205  // Parse for enableAngle bool
206  if (thrusterSDF->HasElement("enableAngle"))
207  {
208  thruster.enableAngle = thrusterSDF->Get<bool>("enableAngle");
209  }
210  else
211  {
212  ROS_ERROR_STREAM("Please specify for each thruster if it should enable "
213  "angle adjustment (for ROS subscription)!");
214  }
215 
216  // OPTIONAL PARAMETERS
217  // Parse individual thruster SDF parameters
218  if (thrusterSDF->HasElement("mappingType"))
219  {
220  thruster.mappingType = thrusterSDF->Get<int>("mappingType");
221  ROS_DEBUG_STREAM("Parameter found - setting <mappingType> to <" <<
222  thruster.mappingType << ">.");
223  }
224  else
225  {
226  thruster.mappingType = 0;
227  ROS_INFO_STREAM("Parameter <mappingType> not found: "
228  "Using default value of <" << thruster.mappingType << ">.");
229  }
230 
231  thruster.maxCmd = this->SdfParamDouble(thrusterSDF, "maxCmd", 1.0);
232  thruster.maxForceFwd =
233  this->SdfParamDouble(thrusterSDF, "maxForceFwd", 250.0);
234  thruster.maxForceRev =
235  this->SdfParamDouble(thrusterSDF, "maxForceRev", -100.0);
236  thruster.maxAngle = this->SdfParamDouble(thrusterSDF, "maxAngle",
237  M_PI / 2);
238 
239  // Push to vector and increment
240  this->thrusters.push_back(thruster);
241  thrusterSDF = thrusterSDF->GetNextElement("thruster");
242  thrusterCounter++;
243  }
244  }
245  else
246  {
247  ROS_WARN_STREAM("No 'thruster' tags in description - how will you move?");
248  }
249  ROS_DEBUG_STREAM("Found " << thrusterCounter << " thrusters");
250 
251  // Initialize the ROS node and subscribe to cmd_drive
252  this->rosnode.reset(new ros::NodeHandle(nodeNamespace));
253 
254  // Advertise joint state publisher to view engines and propellers in rviz
255  // TODO: consider throttling joint_state pub for performance
256  // (every OnUpdate may be too frequent).
257  this->jointStatePub =
258  this->rosnode->advertise<sensor_msgs::JointState>("joint_states", 1);
259  this->jointStateMsg.name.resize(2 * thrusters.size());
260  this->jointStateMsg.position.resize(2 * thrusters.size());
261  this->jointStateMsg.velocity.resize(2 * thrusters.size());
262  this->jointStateMsg.effort.resize(2 * thrusters.size());
263 
264  for (size_t i = 0; i < this->thrusters.size(); ++i)
265  {
266  // Prefill the joint state message with the engine and propeller joint.
267  this->jointStateMsg.name[2 * i] = this->thrusters[i].engineJoint->GetName();
268  this->jointStateMsg.name[2 * i + 1] =
269  this->thrusters[i].propJoint->GetName();
270 
271  // Subscribe to commands for each thruster.
272  this->thrusters[i].cmdSub = this->rosnode->subscribe(
273  this->thrusters[i].cmdTopic, 1, &Thruster::OnThrustCmd,
274  &this->thrusters[i]);
275 
276  // Subscribe to angles for each thruster.
277  if (this->thrusters[i].enableAngle)
278  {
279  this->thrusters[i].angleSub = this->rosnode->subscribe(
280  this->thrusters[i].angleTopic, 1, &Thruster::OnThrustAngle,
281  &this->thrusters[i]);
282  }
283  }
284 
285  this->updateConnection = event::Events::ConnectWorldUpdateBegin(
286  std::bind(&UsvThrust::Update, this));
287 }
288 
290 double UsvThrust::ScaleThrustCmd(const double _cmd, const double _maxCmd,
291  const double _maxPos, const double _maxNeg) const
292 {
293  double val = 0.0;
294  if (_cmd >= 0.0)
295  {
296  val = _cmd / _maxCmd * _maxPos;
297  val = std::min(val, _maxPos);
298  }
299  else
300  {
301  double absMaxNeg = std::abs(_maxNeg);
302  val = _cmd / _maxCmd * absMaxNeg;
303  val = std::max(val, -1.0 * absMaxNeg);
304  }
305  return val;
306 }
307 
309 double UsvThrust::Glf(const double _x, const float _A, const float _K,
310  const float _B, const float _v, const float _C, const float _M) const
311 {
312  return _A + (_K - _A) / (pow(_C + exp(-_B * (_x - _M)), 1.0 / _v));
313 }
314 
316 double UsvThrust::GlfThrustCmd(const double _cmd,
317  const double _maxPos,
318  const double _maxNeg) const
319 {
320  double val = 0.0;
321  if (_cmd > 0.01)
322  {
323  val = this->Glf(_cmd, 0.01f, 59.82f, 5.0f, 0.38f, 0.56f, 0.28f);
324  val = std::min(val, _maxPos);
325  }
326  else if (_cmd < 0.01)
327  {
328  val = this->Glf(_cmd, -199.13f, -0.09f, 8.84f, 5.34f, 0.99f, -0.57f);
329  val = std::max(val, _maxNeg);
330  }
331  return val;
332 }
333 
336 {
337  #if GAZEBO_MAJOR_VERSION >= 8
338  common::Time now = this->world->SimTime();
339  #else
340  common::Time now = this->world->GetSimTime();
341  #endif
342 
343  for (size_t i = 0; i < this->thrusters.size(); ++i)
344  {
345  {
346  std::lock_guard<std::mutex> lock(this->mutex);
347  // Enforce command timeout
348  double dtc = (now - this->thrusters[i].lastCmdTime).Double();
349  if (dtc > this->cmdTimeout && this->cmdTimeout > 0.0)
350  {
351  this->thrusters[i].currCmd = 0.0;
352  ROS_DEBUG_STREAM_THROTTLE(1.0, "[" << i << "] Cmd Timeout");
353  }
354 
355  // Adjust thruster engine joint angle with PID
356  this->RotateEngine(i, now - this->thrusters[i].lastAngleUpdateTime);
357 
358  // Apply the thrust mapping
359  ignition::math::Vector3d tforcev(0, 0, 0);
360  switch (this->thrusters[i].mappingType)
361  {
362  case 0:
363  tforcev.X() = this->ScaleThrustCmd(this->thrusters[i].currCmd/
364  this->thrusters[i].maxCmd,
365  this->thrusters[i].maxCmd,
366  this->thrusters[i].maxForceFwd,
367  this->thrusters[i].maxForceRev);
368  break;
369  case 1:
370  tforcev.X() = this->GlfThrustCmd(this->thrusters[i].currCmd/
371  this->thrusters[i].maxCmd,
372  this->thrusters[i].maxForceFwd,
373  this->thrusters[i].maxForceRev);
374  break;
375  default:
376  ROS_FATAL_STREAM("Cannot use mappingType=" <<
377  this->thrusters[i].mappingType);
378  break;
379  }
380 
381  // Apply force for each thruster
382  this->thrusters[i].link->AddLinkForce(tforcev);
383 
384  // Spin the propellers
385  this->SpinPropeller(i);
386  }
387  }
388 
389  // Publish the propeller joint state
390  this->jointStateMsg.header.stamp = ros::Time::now();
391  this->jointStatePub.publish(this->jointStateMsg);
392 }
393 
395 void UsvThrust::RotateEngine(size_t _i, common::Time _stepTime)
396 {
397  // Calculate angleError for PID calculation
398  double desiredAngle = this->thrusters[_i].desiredAngle;
399  #if GAZEBO_MAJOR_VERSION >= 8
400  double currAngle = this->thrusters[_i].engineJoint->Position(0);
401  #else
402  double currAngle = this->thrusters[_i].engineJoint->GetAngle(0).Radian();
403  #endif
404  double angleError = currAngle - desiredAngle;
405 
406  double effort = this->thrusters[_i].engineJointPID.Update(angleError,
407  _stepTime);
408  this->thrusters[_i].engineJoint->SetForce(0, effort);
409 
410  // Set position, velocity, and effort of joint from gazebo
411  #if GAZEBO_MAJOR_VERSION >= 8
412  ignition::math::Angle position =
413  this->thrusters[_i].engineJoint->Position(0);
414  #else
415  gazebo::math::Angle position = this->thrusters[_i].engineJoint->GetAngle(0);
416  #endif
417  position.Normalize();
418  this->jointStateMsg.position[2 * _i] = position.Radian();
419  this->jointStateMsg.velocity[2 * _i] =
420  this->thrusters[_i].engineJoint->GetVelocity(0);
421  this->jointStateMsg.effort[2 * _i] = effort;
422 
423  // Store last update time
424  this->thrusters[_i].lastAngleUpdateTime += _stepTime;
425 }
426 
429 {
430  const double kMinInput = 0.1;
431  const double kMaxEffort = 2.0;
432  double effort = 0.0;
433 
434  physics::JointPtr propeller = this->thrusters[_i].propJoint;
435 
436  // Calculate effort on propeller joint
437  if (std::abs(this->thrusters[_i].currCmd/
438  this->thrusters[_i].maxCmd) > kMinInput)
439  effort = (this->thrusters[_i].currCmd /
440  this->thrusters[_i].maxCmd) * kMaxEffort;
441 
442  propeller->SetForce(0, effort);
443 
444  // Set position, velocity, and effort of joint from gazebo
445  #if GAZEBO_MAJOR_VERSION >= 8
446  ignition::math::Angle position = propeller->Position(0);
447  #else
448  gazebo::math::Angle position = propeller->GetAngle(0);
449  #endif
450  position.Normalize();
451  this->jointStateMsg.position[2 * _i + 1] = position.Radian();
452  this->jointStateMsg.velocity[2 * _i + 1] = propeller->GetVelocity(0);
453  this->jointStateMsg.effort[2 * _i + 1] = effort;
454 }
455 
double desiredAngle
Most recent desired angle.
#define ROS_DEBUG_STREAM_THROTTLE(rate, args)
GZ_REGISTER_MODEL_PLUGIN(UsvThrust)
void RotateEngine(size_t _i, common::Time _stepTime)
Rotate engine using engine joint PID.
f
physics::WorldPtr world
Pointer to the Gazebo world, retrieved when the model is loaded.
void OnThrustCmd(const std_msgs::Float32::ConstPtr &_msg)
Callback for new thrust commands.
A plugin to simulate a propulsion system under water. This plugin accepts the following SDF parameter...
physics::JointPtr propJoint
Joint controlling the propeller.
physics::LinkPtr link
Link where thrust force is applied.
void OnThrustAngle(const std_msgs::Float32::ConstPtr &_msg)
Callback for new thrust angle commands.
virtual void Update()
Callback executed at every physics update.
double maxForceFwd
Max forward force in Newtons.
double maxForceRev
Max reverse force in Newtons.
common::PID engineJointPID
PID for engine joint angle.
double Glf(const double _x, const float _A, const float _K, const float _B, const float _v, const float _C, const float _M) const
Generalized logistic function (GLF) used for non-linear thruster model.
#define ROS_FATAL_STREAM(args)
Thruster(UsvThrust *_parent)
Constructor.
common::Time lastCmdTime
Last time received a command via ROS topic.
UsvThrust * plugin
Plugin parent pointer - for accessing world, etc.
#define ROS_WARN_STREAM(args)
#define ROS_DEBUG_STREAM(args)
double GlfThrustCmd(const double _cmd, const double _maxPos, const double _maxNeg) const
Uses GLF function to map thrust command to thruster force in Newtons.
void SpinPropeller(size_t _i)
Spin a propeller based on its current command.
common::Time lastAngleUpdateTime
Last time of update.
double ScaleThrustCmd(const double _cmd, const double _max_cmd, const double _max_pos, const double _max_neg) const
Takes ROS Drive commands and scales them by max thrust.
std::mutex mutex
A mutex to protect member variables accessed during OnThustCmd() and Update().
bool enableAngle
If true, thruster will have adjustable angle. If false, thruster will have constant angle...
#define ROS_INFO_STREAM(args)
virtual void Load(physics::ModelPtr _parent, sdf::ElementPtr _sdf)
static Time now()
double SdfParamDouble(sdf::ElementPtr _sdfPtr, const std::string &_paramName, const double _defaultVal) const
Convenience function for getting SDF parameters.
double maxCmd
Maximum abs val of incoming command.
int mappingType
Thruster mapping (0=linear; 1=GLF, nonlinear).
std::string cmdTopic
Topic name for incoming ROS thruster commands.
#define ROS_ERROR_STREAM(args)
std::string angleTopic
Topic name for incoming ROS thruster angle commands.
double maxAngle
Maximum abs val of angle.
physics::JointPtr engineJoint
Joint controlling the engine.
double currCmd
Current, most recent command.
#define ROS_DEBUG(...)


usv_gazebo_plugins
Author(s): Brian Bingham , Carlos Aguero
autogenerated on Thu May 7 2020 03:54:47