/* * Copyright (C) 2016 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include "gazebo/transport/Node.hh" #include "gazebo/transport/Subscriber.hh" #include "gazebo/physics/Joint.hh" #include "gazebo/physics/Link.hh" #include "gazebo/physics/Model.hh" #include "gazebo/physics/PhysicsEngine.hh" #include "gazebo/physics/World.hh" #include "plugins/HarnessPlugin.hh" using namespace gazebo; GZ_REGISTER_MODEL_PLUGIN(HarnessPlugin) ///////////////////////////////////////////////// HarnessPlugin::HarnessPlugin() { } ///////////////////////////////////////////////// HarnessPlugin::~HarnessPlugin() { } ///////////////////////////////////////////////// void HarnessPlugin::Load(physics::ModelPtr _model, sdf::ElementPtr _sdf) { // Get a pointer to the world physics::WorldPtr world = _model->GetWorld(); this->node = transport::NodePtr(new transport::Node()); this->node->Init(world->GetName()); this->velocitySub = this->node->Subscribe( "~/" + _model->GetName() + "/harness/velocity", &HarnessPlugin::OnVelocity, this); this->detachSub = this->node->Subscribe( "~/" + _model->GetName() + "/harness/detach", &HarnessPlugin::OnDetach, this); // Load all the harness joints sdf::ElementPtr jointElem = _sdf->GetElement("joint"); while (jointElem) { std::string jointName = jointElem->Get("name"); try { auto joint = _model->CreateJoint(jointElem); this->joints.push_back(joint); } catch(gazebo::common::Exception &_e) { gzerr << "Unable to load joint[" << jointName << "]. " << _e.GetErrorStr() << std::endl; } jointElem = jointElem->GetNextElement("joint"); } // Make sure at least one joint was created. if (this->joints.empty()) { gzerr << "No joints specified in the harness plugin." << "The harness plugin will not run." << std::endl; return; } // Get the detach joint if (_sdf->HasElement("detach")) { std::string jointName = _sdf->Get("detach"); this->detachIndex = this->JointIndex(jointName); // Error reporting if (this->detachIndex < 0) { this->detachIndex = 0; gzwarn << "Invalid joint name[" << jointName << "] in the " << "harness plugin. The first joint will be used as the detach " << "joint." << std::endl; } } else { // Error reporting gzwarn << "A element is missing from the harness plugin. " << "The first joint will be used as the detach joint." << std::endl; } // Get the winch if (_sdf->HasElement("winch")) { sdf::ElementPtr winchElem = _sdf->GetElement("winch"); // Find the winch joint if (winchElem->HasElement("joint")) { std::string winchJointName = winchElem->Get("joint"); this->winchIndex = this->JointIndex(winchJointName); // Error reporting if (this->winchIndex < 0) { this->winchIndex = 0; gzwarn << "Invalid name[" << winchJointName << "] in the " << " element of the harness plugin.\n" << "The first joint will be used as the winch." << std::endl; } } else { // Error reporting gzwarn << "A joint_name element is " << "missing from the harness plugin.\n" << "The first joint will be used as the winch." << std::endl; } // Load the Position PID controller if (winchElem->HasElement("pos_pid")) { sdf::ElementPtr pidElem = winchElem->GetElement("pos_pid"); double pValue = pidElem->HasElement("p") ? pidElem->Get("p") : 0; double iValue = pidElem->HasElement("i") ? pidElem->Get("i") : 0; double dValue = pidElem->HasElement("d") ? pidElem->Get("d") : 0; double iMax = pidElem->HasElement("i_max") ? pidElem->Get("i_max") : 0; double iMin = pidElem->HasElement("i_min") ? pidElem->Get("i_min") : 0; double cmdMax = pidElem->HasElement("cmd_max") ? pidElem->Get("cmd_max") : 0; double cmdMin = pidElem->HasElement("cmd_min") ? pidElem->Get("cmd_min") : 0; this->winchPosPID.Init(pValue, iValue, dValue, iMax, iMin, cmdMax, cmdMin); } // Load the Velocity PID controller if (winchElem->HasElement("vel_pid")) { sdf::ElementPtr pidElem = winchElem->GetElement("vel_pid"); double pValue = pidElem->HasElement("p") ? pidElem->Get("p") : 0; double iValue = pidElem->HasElement("i") ? pidElem->Get("i") : 0; double dValue = pidElem->HasElement("d") ? pidElem->Get("d") : 0; double iMax = pidElem->HasElement("i_max") ? pidElem->Get("i_max") : 0; double iMin = pidElem->HasElement("i_min") ? pidElem->Get("i_min") : 0; double cmdMax = pidElem->HasElement("cmd_max") ? pidElem->Get("cmd_max") : 0; double cmdMin = pidElem->HasElement("cmd_min") ? pidElem->Get("cmd_min") : 0; this->winchVelPID.Init(pValue, iValue, dValue, iMax, iMin, cmdMax, cmdMin); } } else { // Error reporting gzwarn << "A element is missing from the harness plugin. " << "The first joint will be used as the winch." << std::endl; } } ///////////////////////////////////////////////// void HarnessPlugin::Init() { for (auto &joint : this->joints) { try { joint->Init(); } catch(...) { gzerr << "Init joint failed" << std::endl; return; } } if (!this->joints.empty()) { this->updateConnection = event::Events::ConnectWorldUpdateBegin( std::bind(&HarnessPlugin::OnUpdate, this, std::placeholders::_1)); } } ///////////////////////////////////////////////// void HarnessPlugin::OnUpdate(const common::UpdateInfo &_info) { // Bootstrap the time. if (this->prevSimTime == common::Time::Zero) { this->prevSimTime = _info.simTime; return; } common::Time dt = _info.simTime - this->prevSimTime; // store winchIndex in local variable since it can change in callback int tmpWinchIndex = this->winchIndex; if (tmpWinchIndex < 0 || tmpWinchIndex >= static_cast(this->joints.size())) { if (this->detachIndex >= 0 && this->detachIndex < static_cast(this->joints.size())) { gzmsg << "Detaching harness joint" << std::endl; this->Detach(); } gzerr << "No known winch joint to control" << std::endl; return; } double pError = 0; if (ignition::math::equal(this->winchTargetVel, 0.0f)) { // Calculate the position error if vel target is 0 pError = this->joints[tmpWinchIndex]->GetAngle(0).Radian() - this->winchTargetPos; } // Calculate the velocity error double vError = this->joints[tmpWinchIndex]->GetVelocity(0) - this->winchTargetVel; // Use the PID controller to compute the joint force double winchPosForce = this->winchPosPID.Update(pError, dt); double winchVelForce = this->winchVelPID.Update(vError, dt); // Truncate winchForce so it doesn't push the robot downwards // although this can also be accomplished by cmd_min and cmd_max. winchVelForce = winchVelForce > 0? winchVelForce : 0.0; // Apply the joint force this->joints[tmpWinchIndex]->SetForce(0, winchVelForce + winchPosForce); this->prevSimTime = _info.simTime; } ///////////////////////////////////////////////// void HarnessPlugin::Detach() { if (this->detachIndex < 0 || this->detachIndex >= static_cast(this->joints.size())) { gzerr << "No known joint to detach" << std::endl; return; } const auto detachName = this->joints[this->detachIndex]->GetName(); physics::BasePtr parent = this->joints[this->detachIndex]->Base::GetParent(); auto model = boost::dynamic_pointer_cast(parent); if (!model) { gzerr << "Can't get valid model pointer" << std::endl; return; } // We no longer need to update this->updateConnection.reset(); (this->joints[this->detachIndex]).reset(); model->RemoveJoint(detachName); this->detachIndex = -1; this->winchIndex = -1; this->prevSimTime == common::Time::Zero; } ///////////////////////////////////////////////// double HarnessPlugin::WinchVelocity() const { // store winchIndex in local variable since it can change in callback int tmpWinchIndex = this->winchIndex; if (tmpWinchIndex < 0 || tmpWinchIndex >= static_cast(this->joints.size())) { gzerr << "No known winch joint to get velocity" << std::endl; return 0; } return this->joints[tmpWinchIndex]->GetVelocity(0); } ///////////////////////////////////////////////// void HarnessPlugin::SetWinchVelocity(const float _value) { // store winchIndex in local variable since it can change in callback int tmpWinchIndex = this->winchIndex; if (tmpWinchIndex < 0 || tmpWinchIndex >= static_cast(this->joints.size())) { gzerr << "No known winch joint to set velocity" << std::endl; return; } this->winchTargetVel = _value; if (ignition::math::equal(_value, 0.0f)) { // if zero velocity is commanded, hold position this->winchTargetPos = this->joints[tmpWinchIndex]->GetAngle(0).Radian(); this->winchPosPID.Reset(); } } ///////////////////////////////////////////////// int HarnessPlugin::JointIndex(const std::string &_name) const { // Find the given joint in our list of joints for (size_t i = 0; i < this->joints.size(); ++i) { if (this->joints[i]->GetName() == _name) return i; } return -1; } ///////////////////////////////////////////////// void HarnessPlugin::OnVelocity(ConstGzStringPtr &_msg) { try { this->SetWinchVelocity(std::stof(_msg->data())); } catch(...) { gzerr << "Invalid velocity data[" << _msg->data() << "]\n"; } } ///////////////////////////////////////////////// void HarnessPlugin::OnDetach(ConstGzStringPtr &_msg) { if (_msg->data() == "true" || _msg->data() == "TRUE" || _msg->data() == "True") { this->winchIndex = -1; } }