gazebo_motor_model.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2015 Fadri Furrer, ASL, ETH Zurich, Switzerland
3  * Copyright 2015 Michael Burri, ASL, ETH Zurich, Switzerland
4  * Copyright 2015 Mina Kamel, ASL, ETH Zurich, Switzerland
5  * Copyright 2015 Janosch Nikolic, ASL, ETH Zurich, Switzerland
6  * Copyright 2015 Markus Achtelik, ASL, ETH Zurich, Switzerland
7  * Copyright 2016 Geoffrey Hunter <gbmhunter@gmail.com>
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.M
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21 
23 
24 #include "ConnectGazeboToRosTopic.pb.h"
25 #include "ConnectRosToGazeboTopic.pb.h"
26 
27 namespace gazebo {
28 
30 }
31 
33 
35  if (publish_speed_) {
36  turning_velocity_msg_.set_data(joint_->GetVelocity(0));
38  }
39  if (publish_position_) {
40  position_msg_.set_data(joint_->Position(0));
42  }
43  if (publish_force_) {
44  force_msg_.set_data(joint_->GetForce(0));
45  motor_force_pub_->Publish(force_msg_);
46  }
47 }
48 
49 void GazeboMotorModel::Load(physics::ModelPtr _model, sdf::ElementPtr _sdf) {
50  if (kPrintOnPluginLoad) {
51  gzdbg << __FUNCTION__ << "() called." << std::endl;
52  }
53 
54  model_ = _model;
55 
56  namespace_.clear();
57 
58  if (_sdf->HasElement("robotNamespace"))
59  namespace_ = _sdf->GetElement("robotNamespace")->Get<std::string>();
60  else
61  gzerr << "[gazebo_motor_model] Please specify a robotNamespace.\n";
62 
63  node_handle_ = gazebo::transport::NodePtr(new transport::Node());
64 
65  // Initialise with default namespace (typically /gazebo/default/)
66  node_handle_->Init();
67 
68  if (_sdf->HasElement("jointName"))
69  joint_name_ = _sdf->GetElement("jointName")->Get<std::string>();
70  else
71  gzerr << "[gazebo_motor_model] Please specify a jointName, where the rotor "
72  "is attached.\n";
73 
74  // Get the pointer to the joint.
75  joint_ = model_->GetJoint(joint_name_);
76  if (joint_ == NULL)
77  gzthrow(
78  "[gazebo_motor_model] Couldn't find specified joint \"" << joint_name_
79  << "\".");
80 
81  if (_sdf->HasElement("linkName"))
82  link_name_ = _sdf->GetElement("linkName")->Get<std::string>();
83  else
84  gzerr << "[gazebo_motor_model] Please specify a linkName of the rotor.\n";
85  link_ = model_->GetLink(link_name_);
86  if (link_ == NULL)
87  gzthrow(
88  "[gazebo_motor_model] Couldn't find specified link \"" << link_name_
89  << "\".");
90 
91  if (_sdf->HasElement("motorNumber"))
92  motor_number_ = _sdf->GetElement("motorNumber")->Get<int>();
93  else
94  gzerr << "[gazebo_motor_model] Please specify a motorNumber.\n";
95 
96  if (_sdf->HasElement("turningDirection")) {
97  std::string turning_direction =
98  _sdf->GetElement("turningDirection")->Get<std::string>();
99  if (turning_direction == "cw")
101  else if (turning_direction == "ccw")
103  else
104  gzerr << "[gazebo_motor_model] Please only use 'cw' or 'ccw' as "
105  "turningDirection.\n";
106  } else
107  gzerr << "[gazebo_motor_model] Please specify a turning direction ('cw' or "
108  "'ccw').\n";
109 
110  if (_sdf->HasElement("motorType")) {
111  std::string motor_type = _sdf->GetElement("motorType")->Get<std::string>();
112  if (motor_type == "velocity")
114  else if (motor_type == "position")
116  else if (motor_type == "force") {
118  } else
119  gzerr << "[gazebo_motor_model] Please only use 'velocity', 'position' or "
120  "'force' as motorType.\n";
121  } else {
122  gzwarn << "[gazebo_motor_model] motorType not specified, using velocity.\n";
124  }
125 
126  // Set up joint control PID to control joint.
128  if (_sdf->HasElement("joint_control_pid")) {
129  sdf::ElementPtr pid = _sdf->GetElement("joint_control_pid");
130  double p = 0;
131  if (pid->HasElement("p"))
132  p = pid->Get<double>("p");
133  double i = 0;
134  if (pid->HasElement("i"))
135  i = pid->Get<double>("i");
136  double d = 0;
137  if (pid->HasElement("d"))
138  d = pid->Get<double>("d");
139  double iMax = 0;
140  if (pid->HasElement("iMax"))
141  iMax = pid->Get<double>("iMax");
142  double iMin = 0;
143  if (pid->HasElement("iMin"))
144  iMin = pid->Get<double>("iMin");
145  double cmdMax = 0;
146  if (pid->HasElement("cmdMax"))
147  cmdMax = pid->Get<double>("cmdMax");
148  double cmdMin = 0;
149  if (pid->HasElement("cmdMin"))
150  cmdMin = pid->Get<double>("cmdMin");
151  pids_.Init(p, i, d, iMax, iMin, cmdMax, cmdMin);
152  } else {
153  pids_.Init(0, 0, 0, 0, 0, 0, 0);
154  gzerr << "[gazebo_motor_model] PID values not found, Setting all values "
155  "to zero!\n";
156  }
157  } else {
158  pids_.Init(0, 0, 0, 0, 0, 0, 0);
159  }
160 
161  getSdfParam<std::string>(
162  _sdf, "commandSubTopic", command_sub_topic_, command_sub_topic_);
163  getSdfParam<std::string>(
164  _sdf, "windSpeedSubTopic", wind_speed_sub_topic_, wind_speed_sub_topic_);
165  getSdfParam<std::string>(
166  _sdf, "motorSpeedPubTopic", motor_speed_pub_topic_,
168 
169  // Only publish position and force messages if a topic is specified in sdf.
170  if (_sdf->HasElement("motorPositionPubTopic")) {
171  publish_position_ = true;
173  _sdf->GetElement("motorPositionPubTopic")->Get<std::string>();
174  }
175  if (_sdf->HasElement("motorForcePubTopic")) {
176  publish_force_ = true;
178  _sdf->GetElement("motorForcePubTopic")->Get<std::string>();
179  }
180 
181  getSdfParam<double>(
182  _sdf, "rotorDragCoefficient", rotor_drag_coefficient_,
184  getSdfParam<double>(
185  _sdf, "rollingMomentCoefficient", rolling_moment_coefficient_,
187  getSdfParam<double>(
188  _sdf, "maxRotVelocity", max_rot_velocity_, max_rot_velocity_);
189  getSdfParam<double>(_sdf, "motorConstant", motor_constant_, motor_constant_);
190  getSdfParam<double>(
191  _sdf, "momentConstant", moment_constant_, moment_constant_);
192 
193  getSdfParam<double>(
194  _sdf, "timeConstantUp", time_constant_up_, time_constant_up_);
195  getSdfParam<double>(
196  _sdf, "timeConstantDown", time_constant_down_, time_constant_down_);
197  getSdfParam<double>(
198  _sdf, "rotorVelocitySlowdownSim", rotor_velocity_slowdown_sim_, 10);
199 
200  // Listen to the update event. This event is broadcast every
201  // simulation iteration.
202  updateConnection_ = event::Events::ConnectWorldUpdateBegin(
203  boost::bind(&GazeboMotorModel::OnUpdate, this, _1));
204 
205  // Create the first order filter.
208  time_constant_up_, time_constant_down_, ref_motor_input_));
209 }
210 
211 // This gets called by the world update start event.
212 void GazeboMotorModel::OnUpdate(const common::UpdateInfo& _info) {
213  if (kPrintOnUpdates) {
214  gzdbg << __FUNCTION__ << "() called." << std::endl;
215  }
216 
217  if (!pubs_and_subs_created_) {
219  pubs_and_subs_created_ = true;
220  }
221 
222  sampling_time_ = _info.simTime.Double() - prev_sim_time_;
223  prev_sim_time_ = _info.simTime.Double();
225  Publish();
226 }
227 
229  gzdbg << __PRETTY_FUNCTION__ << " called." << std::endl;
230 
231  // Create temporary "ConnectGazeboToRosTopic" publisher and message
232  gazebo::transport::PublisherPtr gz_connect_gazebo_to_ros_topic_pub =
233  node_handle_->Advertise<gz_std_msgs::ConnectGazeboToRosTopic>(
234  "~/" + kConnectGazeboToRosSubtopic, 1);
235  gz_std_msgs::ConnectGazeboToRosTopic connect_gazebo_to_ros_topic_msg;
236 
237  // Create temporary "ConnectRosToGazeboTopic" publisher and message
238  gazebo::transport::PublisherPtr gz_connect_ros_to_gazebo_topic_pub =
239  node_handle_->Advertise<gz_std_msgs::ConnectRosToGazeboTopic>(
240  "~/" + kConnectRosToGazeboSubtopic, 1);
241  gz_std_msgs::ConnectRosToGazeboTopic connect_ros_to_gazebo_topic_msg;
242 
243  // ============================================ //
244  // ACTUAL MOTOR SPEED MSG SETUP (GAZEBO->ROS) //
245  // ============================================ //
246  if (publish_speed_) {
247  motor_velocity_pub_ = node_handle_->Advertise<gz_std_msgs::Float32>(
248  "~/" + namespace_ + "/" + motor_speed_pub_topic_, 1);
249 
250  connect_gazebo_to_ros_topic_msg.set_gazebo_topic(
251  "~/" + namespace_ + "/" + motor_speed_pub_topic_);
252  connect_gazebo_to_ros_topic_msg.set_ros_topic(
253  namespace_ + "/" + motor_speed_pub_topic_);
254  connect_gazebo_to_ros_topic_msg.set_msgtype(
255  gz_std_msgs::ConnectGazeboToRosTopic::FLOAT_32);
256  gz_connect_gazebo_to_ros_topic_pub->Publish(
257  connect_gazebo_to_ros_topic_msg, true);
258  }
259 
260  // =============================================== //
261  // ACTUAL MOTOR POSITION MSG SETUP (GAZEBO->ROS) //
262  // =============================================== //
263 
264  if (publish_position_) {
265  motor_position_pub_ = node_handle_->Advertise<gz_std_msgs::Float32>(
266  "~/" + namespace_ + "/" + motor_position_pub_topic_, 1);
267 
268  connect_gazebo_to_ros_topic_msg.set_gazebo_topic(
269  "~/" + namespace_ + "/" + motor_position_pub_topic_);
270  connect_gazebo_to_ros_topic_msg.set_ros_topic(
271  namespace_ + "/" + motor_position_pub_topic_);
272  connect_gazebo_to_ros_topic_msg.set_msgtype(
273  gz_std_msgs::ConnectGazeboToRosTopic::FLOAT_32);
274  gz_connect_gazebo_to_ros_topic_pub->Publish(
275  connect_gazebo_to_ros_topic_msg, true);
276  }
277 
278  // ============================================ //
279  // ACTUAL MOTOR FORCE MSG SETUP (GAZEBO->ROS) //
280  // ============================================ //
281 
282  if (publish_force_) {
283  motor_force_pub_ = node_handle_->Advertise<gz_std_msgs::Float32>(
284  "~/" + namespace_ + "/" + motor_force_pub_topic_, 1);
285 
286  connect_gazebo_to_ros_topic_msg.set_gazebo_topic(
287  "~/" + namespace_ + "/" + motor_force_pub_topic_);
288  connect_gazebo_to_ros_topic_msg.set_ros_topic(
289  namespace_ + "/" + motor_force_pub_topic_);
290  connect_gazebo_to_ros_topic_msg.set_msgtype(
291  gz_std_msgs::ConnectGazeboToRosTopic::FLOAT_32);
292  gz_connect_gazebo_to_ros_topic_pub->Publish(
293  connect_gazebo_to_ros_topic_msg, true);
294  }
295 
296  // ============================================ //
297  // = CONTROL COMMAND MSG SETUP (ROS->GAZEBO) = //
298  // ============================================ //
299 
300  command_sub_ = node_handle_->Subscribe(
301  "~/" + namespace_ + "/" + command_sub_topic_,
303 
304  connect_ros_to_gazebo_topic_msg.set_ros_topic(
306  connect_ros_to_gazebo_topic_msg.set_gazebo_topic(
307  "~/" + namespace_ + "/" + command_sub_topic_);
308  connect_ros_to_gazebo_topic_msg.set_msgtype(
309  gz_std_msgs::ConnectRosToGazeboTopic::COMMAND_MOTOR_SPEED);
310  gz_connect_ros_to_gazebo_topic_pub->Publish(
311  connect_ros_to_gazebo_topic_msg, true);
312 
313  // ============================================ //
314  // ==== WIND SPEED MSG SETUP (ROS->GAZEBO) ==== //
315  // ============================================ //
316 
318  wind_speed_sub_ = node_handle_->Subscribe(
319  "~/" + namespace_ + "/" + wind_speed_sub_topic_,
321 
322  connect_ros_to_gazebo_topic_msg.set_ros_topic(
324  connect_ros_to_gazebo_topic_msg.set_gazebo_topic(
325  "~/" + namespace_ + "/" + wind_speed_sub_topic_);
326  connect_ros_to_gazebo_topic_msg.set_msgtype(
327  gz_std_msgs::ConnectRosToGazeboTopic::WIND_SPEED);
328  gz_connect_ros_to_gazebo_topic_pub->Publish(
329  connect_ros_to_gazebo_topic_msg, true);
330 }
331 
333  GzCommandMotorInputMsgPtr& command_motor_input_msg) {
334  if (kPrintOnMsgCallback) {
335  gzdbg << __FUNCTION__ << "() called." << std::endl;
336  }
337 
338  if (motor_number_ > command_motor_input_msg->motor_speed_size() - 1) {
339  gzerr << "You tried to access index " << motor_number_
340  << " of the MotorSpeed message array which is of size "
341  << command_motor_input_msg->motor_speed_size();
342  }
343 
345  ref_motor_input_ = std::min(
346  static_cast<double>(
347  command_motor_input_msg->motor_speed(motor_number_)),
348  static_cast<double>(max_rot_velocity_));
349  } else if (motor_type_ == MotorType::kPosition) {
350  ref_motor_input_ = command_motor_input_msg->motor_speed(motor_number_);
351  } else { // if (motor_type_ == MotorType::kForce) {
352  ref_motor_input_ = std::min(
353  static_cast<double>(
354  command_motor_input_msg->motor_speed(motor_number_)),
355  static_cast<double>(max_force_));
356  }
357 }
358 
360  if (kPrintOnMsgCallback) {
361  gzdbg << __FUNCTION__ << "() called." << std::endl;
362  }
363 
364  // TODO(burrimi): Transform velocity to world frame if frame_id is set to
365  // something else.
366  wind_speed_W_.X() = wind_speed_msg->velocity().x();
367  wind_speed_W_.Y() = wind_speed_msg->velocity().y();
368  wind_speed_W_.Z() = wind_speed_msg->velocity().z();
369 }
370 
371 double GazeboMotorModel::NormalizeAngle(double input){
372  // Constrain magnitude to be max 2*M_PI.
373  double wrapped = std::fmod(std::abs(input), 2*M_PI);
374  wrapped = std::copysign(wrapped, input);
375 
376  // Constrain result to be element of [0, 2*pi).
377  // Set angle to zero if sufficiently close to 2*pi.
378  if(std::abs(wrapped - 2*M_PI) < 1e-8){
379  wrapped = 0;
380  }
381 
382  // Ensure angle is positive.
383  if(wrapped < 0){
384  wrapped += 2*M_PI;
385  }
386 
387  return wrapped;
388 }
389 
391  switch (motor_type_) {
392  case (MotorType::kPosition): {
393  double err = NormalizeAngle(joint_->Position(0)) - NormalizeAngle(ref_motor_input_);
394 
395  // Angles are element of [0..2pi).
396  // Constrain difference of angles to be in [-pi..pi).
397  if (err > M_PI){
398  err -= 2*M_PI;
399  }
400  if (err < -M_PI){
401  err += 2*M_PI;
402  }
403  if(std::abs(err - M_PI) < 1e-8){
404  err = -M_PI;
405  }
406 
407  double force = pids_.Update(err, sampling_time_);
408  joint_->SetForce(0, force);
409  break;
410  }
411  case (MotorType::kForce): {
412  joint_->SetForce(0, ref_motor_input_);
413  break;
414  }
415  default: // MotorType::kVelocity
416  {
417  motor_rot_vel_ = joint_->GetVelocity(0);
418  if (motor_rot_vel_ / (2 * M_PI) > 1 / (2 * sampling_time_)) {
419  gzerr << "Aliasing on motor [" << motor_number_
420  << "] might occur. Consider making smaller simulation time "
421  "steps or raising the rotor_velocity_slowdown_sim_ param.\n";
422  }
423  double real_motor_velocity =
425  // Get the direction of the rotor rotation.
426  int real_motor_velocity_sign =
427  (real_motor_velocity > 0) - (real_motor_velocity < 0);
428  // Assuming symmetric propellers (or rotors) for the thrust calculation.
429  double thrust = turning_direction_ * real_motor_velocity_sign *
430  real_motor_velocity * real_motor_velocity *
432 
433  // Apply a force to the link.
434  link_->AddRelativeForce(ignition::math::Vector3d (0, 0, thrust));
435 
436  // Forces from Philppe Martin's and Erwan Salaün's
437  // 2010 IEEE Conference on Robotics and Automation paper
438  // The True Role of Accelerometer Feedback in Quadrotor Control
439  // - \omega * \lambda_1 * V_A^{\perp}
440  ignition::math::Vector3d joint_axis = joint_->GlobalAxis(0);
441  ignition::math::Vector3d body_velocity_W = link_->WorldLinearVel();
442  ignition::math::Vector3d relative_wind_velocity_W = body_velocity_W - wind_speed_W_;
443  ignition::math::Vector3d body_velocity_perpendicular =
444  relative_wind_velocity_W -
445  (relative_wind_velocity_W.Dot(joint_axis) * joint_axis);
446  ignition::math::Vector3d air_drag = -std::abs(real_motor_velocity) *
448  body_velocity_perpendicular;
449 
450  // Apply air_drag to link.
451  link_->AddForce(air_drag);
452  // Moments get the parent link, such that the resulting torques can be
453  // applied.
454  physics::Link_V parent_links = link_->GetParentJointsLinks();
455  // The tansformation from the parent_link to the link_.
456  ignition::math::Pose3d pose_difference =
457  link_->WorldCoGPose() - parent_links.at(0)->WorldCoGPose();
458  ignition::math::Vector3d drag_torque(
459  0, 0, -turning_direction_ * thrust * moment_constant_);
460  // Transforming the drag torque into the parent frame to handle
461  // arbitrary rotor orientations.
462  ignition::math::Vector3d drag_torque_parent_frame =
463  pose_difference.Rot().RotateVector(drag_torque);
464  parent_links.at(0)->AddRelativeTorque(drag_torque_parent_frame);
465 
466  ignition::math::Vector3d rolling_moment;
467  // - \omega * \mu_1 * V_A^{\perp}
468  rolling_moment = -std::abs(real_motor_velocity) *
470  body_velocity_perpendicular;
471  parent_links.at(0)->AddTorque(rolling_moment);
472  // Apply the filter on the motor's velocity.
473  double ref_motor_rot_vel;
474  ref_motor_rot_vel = rotor_velocity_filter_->updateFilter(
476 
477  // Make sure max force is set, as it may be reset to 0 by a world reset any
478  // time. (This cannot be done during Reset() because the change will be undone
479  // by the Joint's reset function afterwards.)
480  joint_->SetVelocity(
481  0, turning_direction_ * ref_motor_rot_vel /
482  rotor_velocity_slowdown_sim_);
483  }
484  }
485 }
486 
488 }
d
static const int CCW
void WindSpeedCallback(GzWindSpeedMsgPtr &wind_speed_msg)
event::ConnectionPtr updateConnection_
Pointer to the update event connection.
void CreatePubsAndSubs()
Creates all required publishers and subscribers, incl. routing of messages to/from ROS if required...
double motor_rot_vel_
Definition: motor_model.hpp:47
static const std::string kConnectRosToGazeboSubtopic
Definition: common.h:57
virtual void Load(physics::ModelPtr _model, sdf::ElementPtr _sdf)
gazebo::transport::NodePtr node_handle_
gazebo::transport::PublisherPtr motor_position_pub_
gazebo::transport::SubscriberPtr wind_speed_sub_
gazebo::transport::PublisherPtr motor_velocity_pub_
#define M_PI
ignition::math::Vector3d wind_speed_W_
gz_std_msgs::Float32 position_msg_
gz_std_msgs::Float32 turning_velocity_msg_
double prev_sim_time_
Definition: motor_model.hpp:50
double NormalizeAngle(double input)
Ensures any input angle is element of [0..2pi)
std::unique_ptr< FirstOrderFilter< double > > rotor_velocity_filter_
static const bool kPrintOnPluginLoad
Definition: common.h:41
static const int CW
bool pubs_and_subs_created_
Flag that is set to true once CreatePubsAndSubs() is called, used to prevent CreatePubsAndSubs() from...
gz_std_msgs::Float32 force_msg_
virtual void OnUpdate(const common::UpdateInfo &)
gazebo::transport::PublisherPtr motor_force_pub_
void ControlCommandCallback(GzCommandMotorInputMsgPtr &command_motor_input_msg)
static const bool kPrintOnUpdates
Definition: common.h:42
GZ_REGISTER_MODEL_PLUGIN(GazeboRosP3D)
static const std::string kConnectGazeboToRosSubtopic
Definition: common.h:56
gazebo::transport::SubscriberPtr command_sub_
double sampling_time_
Definition: motor_model.hpp:51
static const bool kPrintOnMsgCallback
Definition: common.h:43
This class can be used to apply a first order filter on a signal. It allows different acceleration an...
Definition: common.h:148


rotors_gazebo_plugins
Author(s): Fadri Furrer, Michael Burri, Mina Kamel, Janosch Nikolic, Markus Achtelik
autogenerated on Mon Feb 28 2022 23:39:03