Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

OK I found where my problem had stemmed from. The code here is OK as others kindly pointed out in the comments. what was wrong, was the broadcaster that I had written for sending the trutle1/turtle2 transformations.

basically I was instantiating a new broadcaster in my callback and this was the culprit. I had to either make it static or use one instance for this to work(why is that an issue? I still don't know).

So this is my broadcaster I provided some explanation concerning the issue in the comments of callback_fn:

 #include <iostream>
#include <string>
#include <functional>

#include <ros/ros.h>
#include <tf2_ros/transform_broadcaster.h>
#include <geometry_msgs/TransformStamped.h>
#include <turtlesim/Pose.h>
#include <tf/LinearMath/Quaternion.h>

int main(int argc, char** argv)
{
    ros::init(argc, argv, "tf2_broadcaster_node");
    ros::NodeHandle n;
    ros::NodeHandle tmp_node("~");
    std::string turtle_name = "";

    if (!tmp_node.hasParam("turtle"))
    {
        if (argc != 2)
        {
            ROS_ERROR("Number of arguments do not match! must provide a turtle's name as the argument!");
            return -1;
        }
        else
        {
            // Get the second argument which is the turtlename (the first argument is the applications exe path)
            turtle_name = std::string(argv[1]);
            std::cout<<"turtle_name is: "<<turtle_name<<std::endl;
        }
    }
    else
    {
        // if we already have a turtlename in our parameter server, fetch it!
        tmp_node.getParam("turtle", turtle_name);
        std::cout<<"turtle_name: "<<turtle_name<<std::endl;
    }

    // see the comment below, either uncomment this or 
    // the static one below. see the explanation for more details
    //tf2_ros::TransformBroadcaster broadcaster;
    auto callback_fn = [&](turtlesim::PoseConstPtr pose)
    {
        //Its very important that broadcaster is shared between all turtles
        //that is if you define broadcaster in this function/it must be static
        //otherwise, you will endup with multiple broadcasters, sending coords
        // which will mess everything up. previously I forgot about this
        // and when I ran the listener app which relies on this, the trutle2 was
        // moving eratically, in circles, it would get close to turtle1, but not always
        // and not as fast, it would take alot of time and huge circles! remove static and 
        // see it yourself.(run tf2_transform_carrot_demo.launch)
        static tf2_ros::TransformBroadcaster broadcaster;
        geometry_msgs::TransformStamped transform;
        tf::Quaternion qtr;

        // since we want all the nodes to be linked together, here we are specifying
        // one parent called "world" for this node. any other nodes that have this as 
        // parent will be able to communicate with this node. and because our turtles
        // need to be linked in order to  communicate through tf2 this is necessary
        transform.header.frame_id = "world";
        transform.header.stamp = ros::Time::now();
        //this means we are naming our frame after turtlename
        // and our parent frame is world, this allows us to
        // be with other frames that already exist and
        // world is their parent (i.e. this way we can have multiple turtles
        // roaming in the same world)
        transform.child_frame_id = turtle_name;

        // as turtlebot only moves in 2d coord, we only
        // have x, and y and not z.
        transform.transform.translation.x = pose->x;
        transform.transform.translation.y = pose->y;
        transform.transform.translation.z = 0;
        // again turtlebot only moves in 2d, so there is no roll or pitch
        // we only have yaw. remember that angles are in radian in ros.
        qtr.setRPY(0, 0, pose->theta);

        transform.transform.rotation.w = qtr.w();
        transform.transform.rotation.x = qtr.x();
        transform.transform.rotation.y = qtr.y();
        transform.transform.rotation.z = qtr.z();

        // Note:
        // sendTransform and StampedTransform have opposite ordering of parent and child.
        // Note2:
        // You can also publish static transforms on the same pattern by instantiating
        // a StaticTransformBroadcaster (from tf2_ros/static_transform_broadcaster.h) instead
        // of a TransformBroadcaster. The static transforms will be published on the
        // /tf_static topic (as apposed to /tf) and will be sent only when required(latched topic)
        // and not periodically. For more details see here
        broadcaster.sendTransform(transform);
        ROS_INFO("turtlename: %s", turtle_name.c_str());
    };

    ROS_INFO("-turtlename: %s", turtle_name.c_str());

    ros::Subscriber subscriber = n.subscribe<turtlesim::Pose>(turtle_name + "/pose", 1000, callback_fn);
    ros::spin();

    return 0;
}

I'd be greateful if someone could explain why it has to be one instance of the broadcaster for this to work and what happens when its not. (why the eratic behavior?) Thanks a lot

OK I found where my problem had stemmed from. The code here is OK as others kindly pointed out in the comments. what was wrong, was the broadcaster that I had written for sending the trutle1/turtle2 transformations.

basically I was instantiating a new broadcaster in my callback and this was the culprit. I had to either make it static or use one instance for this to work(why is that an issue? I still don't know).

So this is my broadcaster I provided some explanation concerning the issue in the comments of callback_fn:

 #include <iostream>
#include <string>
#include <functional>

#include <ros/ros.h>
#include <tf2_ros/transform_broadcaster.h>
#include <geometry_msgs/TransformStamped.h>
#include <turtlesim/Pose.h>
#include <tf/LinearMath/Quaternion.h>

int main(int argc, char** argv)
{
    ros::init(argc, argv, "tf2_broadcaster_node");
    ros::NodeHandle n;
    ros::NodeHandle tmp_node("~");
    std::string turtle_name = "";

    if (!tmp_node.hasParam("turtle"))
    {
        if (argc != 2)
        {
            ROS_ERROR("Number of arguments do not match! must provide a turtle's name as the argument!");
            return -1;
        }
        else
        {
            // Get the second argument which is the turtlename (the first argument is the applications exe path)
            turtle_name = std::string(argv[1]);
            std::cout<<"turtle_name is: "<<turtle_name<<std::endl;
        }
    }
    else
    {
        // if we already have a turtlename in our parameter server, fetch it!
        tmp_node.getParam("turtle", turtle_name);
        std::cout<<"turtle_name: "<<turtle_name<<std::endl;
    }

    // see the comment below, either uncomment this or 
    // the static one below. see the explanation for more details
    //tf2_ros::TransformBroadcaster broadcaster;
    auto callback_fn = [&](turtlesim::PoseConstPtr pose)
    {
        //Its very important that broadcaster is shared between all turtles
        //that is if you define broadcaster in this function/it must be static
        //otherwise, you will endup with multiple broadcasters, sending coords
        // which will mess everything up. previously I forgot about this
        // and when I ran the listener app which relies on this, the trutle2 was
        // moving eratically, in circles, it would get close to turtle1, but not always
        // and not as fast, it would take alot of time and huge circles! remove static and 
        // see it yourself.(run tf2_transform_carrot_demo.launch)
        static tf2_ros::TransformBroadcaster broadcaster;
        geometry_msgs::TransformStamped transform;
        tf::Quaternion qtr;

        // since we want all the nodes to be linked together, here we are specifying
        // one parent called "world" for this node. any other nodes that have this as 
        // parent will be able to communicate with this node. and because our turtles
        // need to be linked in order to  communicate through tf2 this is necessary
        transform.header.frame_id = "world";
        transform.header.stamp = ros::Time::now();
        //this means we are naming our frame after turtlename
        // and our parent frame is world, this allows us to
        // be with other frames that already exist and
        // world is their parent (i.e. this way we can have multiple turtles
        // roaming in the same world)
        transform.child_frame_id = turtle_name;

        // as turtlebot only moves in 2d coord, we only
        // have x, and y and not z.
        transform.transform.translation.x = pose->x;
        transform.transform.translation.y = pose->y;
        transform.transform.translation.z = 0;
        // again turtlebot only moves in 2d, so there is no roll or pitch
        // we only have yaw. remember that angles are in radian in ros.
        qtr.setRPY(0, 0, pose->theta);

        transform.transform.rotation.w = qtr.w();
        transform.transform.rotation.x = qtr.x();
        transform.transform.rotation.y = qtr.y();
        transform.transform.rotation.z = qtr.z();

        // Note:
        // sendTransform and StampedTransform have opposite ordering of parent and child.
        // Note2:
        // You can also publish static transforms on the same pattern by instantiating
        // a StaticTransformBroadcaster (from tf2_ros/static_transform_broadcaster.h) instead
        // of a TransformBroadcaster. The static transforms will be published on the
        // /tf_static topic (as apposed to /tf) and will be sent only when required(latched topic)
        // and not periodically. For more details see here
        broadcaster.sendTransform(transform);
        ROS_INFO("turtlename: %s", turtle_name.c_str());
    };

    ROS_INFO("-turtlename: %s", turtle_name.c_str());

    ros::Subscriber subscriber = n.subscribe<turtlesim::Pose>(turtle_name + "/pose", 1000, callback_fn);
    ros::spin();

    return 0;
}

Doing so results in a prefect turtle hide and seek :
image description

also I'd be greateful grateful if someone could explain why it has to be one instance of the broadcaster for this to work and what happens when its not. (why the eratic behavior?) Thanks a lot

OK I found where my problem had stemmed from. The code here is OK as others kindly pointed out in the comments. what was wrong, was the broadcaster that I had written for sending the trutle1/turtle2 transformations.

basically I was instantiating a new broadcaster in my callback and this was the culprit. I had to either make it static or use one instance for this to work(why is that an issue? I still don't know).

So this is my broadcaster I provided some explanation concerning the issue in the comments of callback_fn:

 #include <iostream>
#include <string>
#include <functional>

#include <ros/ros.h>
#include <tf2_ros/transform_broadcaster.h>
#include <geometry_msgs/TransformStamped.h>
#include <turtlesim/Pose.h>
#include <tf/LinearMath/Quaternion.h>

int main(int argc, char** argv)
{
    ros::init(argc, argv, "tf2_broadcaster_node");
    ros::NodeHandle n;
    ros::NodeHandle tmp_node("~");
    std::string turtle_name = "";

    if (!tmp_node.hasParam("turtle"))
    {
        if (argc != 2)
        {
            ROS_ERROR("Number of arguments do not match! must provide a turtle's name as the argument!");
            return -1;
        }
        else
        {
            // Get the second argument which is the turtlename (the first argument is the applications exe path)
            turtle_name = std::string(argv[1]);
            std::cout<<"turtle_name is: "<<turtle_name<<std::endl;
        }
    }
    else
    {
        // if we already have a turtlename in our parameter server, fetch it!
        tmp_node.getParam("turtle", turtle_name);
        std::cout<<"turtle_name: "<<turtle_name<<std::endl;
    }

    // see the comment below, either uncomment this or 
    // the static one below. see the explanation for more details
    //tf2_ros::TransformBroadcaster broadcaster;
    auto callback_fn = [&](turtlesim::PoseConstPtr pose)
    {
        //Its // read https://answers.ros.org/question/381937/
        // short story: it doesn't have to be defined as static or be shared!
        // what matters is that they shouldn't be created/destroyed very fast
        // that is every few milliseconds e.g.
        // or as jvdhoorn says : 
        //  "What is important that broadcaster is shared between all turtles
        //that is if you define broadcaster in this function/it must be static
        //otherwise, to make sure it has a sane lifecycle 
        // (ie: it does not get created and destroyed every few milliseconds)."
        // otherwise, you will endup end-up with multiple broadcasters, sending coords
        // which will mess everything up. all sorts of nonsensical weird happenings
        // previously I forgot about this
        // this and when I ran the listener app which relies 
        // on this, the trutle2 was
        // was moving eratically, erratically, in circles, it would get close to to
        // turtle1, but not always
        // always and not as fast, it would take alot a lot of time and huge huge
        // circles! remove static and 
        // see it yourself.(run tf2_transform_carrot_demo.launch)
yourself.
        // (run tf2_transform_carrot_demo.launch and see the result)
        static tf2_ros::TransformBroadcaster broadcaster;
        geometry_msgs::TransformStamped transform;
        tf::Quaternion qtr;

        // since we want all the nodes to be linked together, here we are specifying
        // one parent called "world" for this node. any other nodes that have this as 
        // parent will be able to communicate with this node. and because our turtles
        // need to be linked in order to  communicate through tf2 this is necessary
        transform.header.frame_id = "world";
        transform.header.stamp = ros::Time::now();
        //this means we are naming our frame after turtlename
        // and our parent frame is world, this allows us to
        // be with other frames that already exist and
        // world is their parent (i.e. this way we can have multiple turtles
        // roaming in the same world)
        transform.child_frame_id = turtle_name;

        // as turtlebot only moves in 2d coord, we only
        // have x, and y and not z.
        transform.transform.translation.x = pose->x;
        transform.transform.translation.y = pose->y;
        transform.transform.translation.z = 0;
        // again turtlebot only moves in 2d, so there is no roll or pitch
        // we only have yaw. remember that angles are in radian in ros.
        qtr.setRPY(0, 0, pose->theta);

        transform.transform.rotation.w = qtr.w();
        transform.transform.rotation.x = qtr.x();
        transform.transform.rotation.y = qtr.y();
        transform.transform.rotation.z = qtr.z();

        // Note:
        // sendTransform and StampedTransform have opposite ordering of parent and child.
        // Note2:
        // You can also publish static transforms on the same pattern by instantiating
        // a StaticTransformBroadcaster (from tf2_ros/static_transform_broadcaster.h) instead
        // of a TransformBroadcaster. The static transforms will be published on the
        // /tf_static topic (as apposed to /tf) and will be sent only when required(latched topic)
        // and not periodically. For more details see here
        broadcaster.sendTransform(transform);
        ROS_INFO("turtlename: %s", turtle_name.c_str());
    };

    ROS_INFO("-turtlename: %s", turtle_name.c_str());

    ros::Subscriber subscriber = n.subscribe<turtlesim::Pose>(turtle_name + "/pose", 1000, callback_fn);
    ros::spin();

    return 0;
}

Doing so results in a prefect turtle hide and seek :
image description

also I'd be grateful if someone could explain why it has to be one instance of the broadcaster for this to work and what happens when its not. (why the eratic behavior?) behavior?)

Update: OK I asked the question here and here is the answer :
in short :

gvdhoorn:
All you need to do is make sure you create your ros::TransformBroadcaster in a scope which lives longer than that of the callback which is using it. For the code you show in #q381845, that would basically mean the scope of main(..) (as you are using lambdas to implement your callbacks). In more traditional code, you'd make the broadcaster a member variable of a class, or keep it in global scope (although that comes with its own disadvantages).

Thanks a lot