Reputation: 641
I am working through the Gazebo tutorial to connect a Gazebo sensor to ROS and pass messages. http://gazebosim.org/tutorials?cat=guided_i&tut=guided_i6
This program builds a Gazebo ModelPlugin object, and initializes ROS from within this object. Then, it creates a ROS node, subscriber, queue, and a standard thread to run the ROS queue. This program works using Gazebo's transport objects, but when I try to add in ROS transport objects (as noted previously), the program does not work. My issue stems from the fact that the roscore nodes (including rosmaster) do not initialize.
My code for the sensor plugin is below. ROS integration begins on line 70:
#ifndef _VELODYNE_PLUGIN_HH_
#define _VELODYNE_PLUGIN_HH_
#include <gazebo/gazebo.hh>
#include <gazebo/msgs/msgs.hh>
#include <gazebo/physics/physics.hh>
#include <gazebo/transport/transport.hh>
#include <ros/ros.h>
#include <ros/callback_queue.h>
#include <ros/subscribe_options.h>
#include <thread>
#include <std_msgs/Float32.h>
namespace gazebo
{
/// \brief A plugin to control a Velodyne sensor.
class VelodynePlugin : public ModelPlugin
{
/// \brief Constructor
public: VelodynePlugin() {}
/// \brief The load function is called by Gazebo when the plugin is
/// inserted into simulation
/// \param[in] _model A pointer to the model that this plugin is
/// attached to.
/// \param[in] _sdf A pointer to the plugin's SDF element.
public: virtual void Load(physics::ModelPtr _model, sdf::ElementPtr _sdf) {
gzwarn << "HERE";
if (_model->GetJointCount() == 0) {
std::cerr <<
"Invalid joint count, Velodyne plugin not loaded\n";
}
// Store the model pointer for convenience.
this->model = _model;
// Get the first joint. We are making an assumption about the
// model having one joint that is the rotational joint.
this->joint = _model->GetJoints()[0];
// Setup a P-controller with a gain of 0.1.
this->pid=common::PID(0.1,0,0);
// Apply the P-controller to the joint.
this->model->GetJointController()->SetVelocityPID(
this->joint->GetScopedName(), this->pid);
// Default to zero velocity
double velocity=0;
// Check that the velocity element exists, then read the value
if (_sdf->HasElement("velocity"))
velocity=_sdf->Get<double>("velocity");
this->SetVelocity(velocity);
// Create the node
this->node = transport::NodePtr(new transport::Node());
this->node->Init(this->model->GetWorld()->GetName());
// Create a topic name
std::string topicName = "~/" + this->model->GetName() +
"/vel_cmd";
// Subscribe to the topic, and register a callback.
this->sub = this->node->Subscribe(topicName,
&VelodynePlugin::OnMsg, this);
// Initialize ros, if it has not already been initialized.
if (!ros::isInitialized()) {
std::cout << "initializing ros" << std::endl;
int argc = 0;
char **argv=NULL;
ros::init(argc,argv,"gazebo_client",
ros::init_options::NoSigintHandler);
} else { std::cout << "NOT initializing ros" << std::endl; }
// Create our ROS node. This acts in a similar manner to the
// Gazebo node.
this->rosNode.reset(new ros::NodeHandle("gazebo_client"));
// Create a named topic, and subscribe to it.
ros::SubscribeOptions so =
ros::SubscribeOptions::create<std_msgs::Float32>(
"/"+this->model->GetName()+"/vel_cmd",
1,
boost::bind(&VelodynePlugin::OnRosMsg, this, _1),
ros::VoidPtr(), &this->rosQueue);
this->rosSub = this->rosNode->subscribe(so);
// Spin up the queue helper thread
this->rosQueueThread =
std::thread(std::bind(&VelodynePlugin::QueueThread,this));
}
/// \brief Set the velocity of the Velodyne
/// \param[in] _vel New target velocity
public: void SetVelocity(const double &_vel) {
// Set the joint's target velocity.
this->model->GetJointController()->SetVelocityTarget(
this->joint->GetScopedName(), _vel);
}
/// \brief Handle incoming message
/// \param[in] _msg Repurpose a vector3 message. This function will
/// only use the x component.
private: void OnMsg(ConstVector3dPtr &_msg) {
this->SetVelocity(_msg->x());
}
/// \brief Handle an incoming message from ROS
/// \param[in] _msg A float value that is used to set the velocity
/// of the Velodyne.
public: void OnRosMsg(const std_msgs::Float32ConstPtr &_msg) {
this->SetVelocity(_msg->data);
}
/// \brief ROS helper function that processes messages
private: void QueueThread() {
static const double timeout = .01;
while (this->rosNode->ok()) {
this->rosQueue.callAvailable(ros::WallDuration(timeout));
}
}
/// \brief Pointer to the model;
private: physics::ModelPtr model;
/// \brief Control surfaces joints.
private: physics::JointPtr joint;
/// \brief Velocity PID for the propeller.
private: common::PID pid;
/// \brief A node used for transport
private: transport::NodePtr node;
/// \brief A subscriber to a named topic.
private: transport::SubscriberPtr sub;
/// \brief A node used for ROS transport
private: std::unique_ptr<ros::NodeHandle> rosNode;
/// \brief A ROS subscriber
private: ros::Subscriber rosSub;
/// \brief A ROS callbackqueue that helps process messages
private: ros::CallbackQueue rosQueue;
/// \brief A thread that keeps running the rosQueue
private: std::thread rosQueueThread;
};
// Tell Gazebo about this plugin, so that Gazebo can call Load on this plugin.
GZ_REGISTER_MODEL_PLUGIN(VelodynePlugin)
}
#endif
Let me know how I can be more specific about this question. The first problem is definitely that ROS is not initialized. I can tell this because a call in a different terminal to rostopic list
outputs:
ERROR: Unable to communicate with master
Upvotes: 1
Views: 2386
Reputation: 1
To my understanding, Gazebo is not part of ROS, so it makes sense if it doesn't start roscore
.
If you want to start roscore
simultaneously as this node starts, try to use roslaunch
command instead of rosrun.
Upvotes: 0
Reputation: 1
The problem is solved via running roscore
beforehand you start Gazebo server. I must accept that the tutorial does, indeed, follows a Gazebo-first pipeline. Nonetheless, I believe it's a deprecated method to make the bridge between ROS and Gazebo. You should inspect the code in gazebo-ros-pkgs for a much easier method.
A Gazebo plugin is a run-time component -shared library in technical terms- that is attached to a specific object or directly to the World instance via SDF/URDF files. A world instance can be simulated through the command gzserver <world_file>
. If you are using Gazebo in conjunction with ROS (I highly recommend that if you are going to use ROS at some point), what you need is gazebo-ros node. Again in the above link, there are several examples of how it is done.
In ROS universe, all the processes are represented as ROS nodes and Gazebo simulator is not different. When you use gazebo_ros
package (which makes the ROS-Gazebo connection in one-line) Gazebo simulator is initialized as /gazebo
node with the specified world file and all the attached Gazebo plugins do operate as components within this node. The first issue resides in here. You should not call ros::init()
in a Gazebo plugin since it has already an initialized node. Second problem is in your assumption, such that ros::init()
call does not start the roscore
node. It only initializes your executable in a way that it can communicate with other ROS nodes under the same master. So, what you need is only a ros::NodeHandle.
I don't know why OSRF hasn't updated the tutorials. Perhaps, they are strictly trying to offer a Gazebo-first aspect in Gazebo website and treat ROS as a secondary component. However I find it rather problematic, especially when one wants to use ROS as primary component. Since roslaunch
only launches ROS nodes, which makes gzserver <world_file>
command disfunctional, this tutorial would be invalid and even contradicting with the mainstream usage.
Upvotes: 0
Reputation: 574
The problem is indeed roscore
but after that you can check the node ur started with rosnode info nodename
to check the sub and publisher status or do roswtf
to check for error in general.
You can check realtime debug log setting ros debug level with rqt logger level(easiest way) to know if your node is getting data or not
Upvotes: -1
Reputation: 5017
It sounds to me like roscore
is not running. You have to run roscore
manually before you can start any ROS node.
You can imagine roscore
as a server to which all nodes connect and that manages the communication between these nodes. It won't start automatically, so you always have to start roscore
as a first step, before you can use any ROS nodes.
An exception to this is, if you are using launch-files. roslaunch
will indeed automatically start roscore
if it is not already running.
Upvotes: 3