0
$\begingroup$

Rosanswers logo

Situation

I'm part of a larger team working with robots and ROS. My current project is to create easy-to-use libraries for navigation and other things from existing code in our repositories. Over time, many people in our lab have implemented the same function a bit differently and I want to unify and streamline these implementations in one single library.

Now, the library has to publish on and subscribe to multiple topics and therefore it requires a ros::NodeHandle to work with. So far my library is purely static (i.e. no classes are instantiated, static members are called directly). This has the advantage, that my library has global awareness and I can prevent many user/robot/real-time errors. Furthermore, subscribing to topics takes a bit of time and messages can be missed. This is another argument for static members, since I can have a static subscriber and initialize it when the node is created.

Problem

When I have a ros::NodeHandle as a static class member, it is initialized right on program-launch, before ros::init() can be called. This returns an error as soon as the rosnode using my library is launched - makes sense.

My Solution

I thought I could perform lazy-initialization of my static members, including subscribers, publishers and the nodehandle. In order to achieve this, all ros objects are static pointers, that I initialize right before first use. My classes all look something like this.

// Class used to actually send various commands to robot
class RobotHelper {
public:
  static int sendCommand(...);

private:
  static void initialize();
  static void callback(const my_msgs::Telemetry &msg);

  static bool initialized;
  static std::unique_ptr<ros::NodeHandle> nh_ptr_;
  static std::unique_ptr<ros::Publisher> cmd_pub_ptr_;
  static std::unique_ptr<ros::Subscriber> telemetry_sub_ptr_;
};

int RobotHelper::sendCommand(...) {
  // Lazy-initialize the RobotHelper
  initialize();

  // Uses telemetry and other things to make sure nothing dangeorus happens when
  // command is sent
  // Also uses telemetry to make sure the robot accepted the command, so
  // we don't have to worry about that later
  // ...
  // ...

  return 0;
}

void RobotHelper::initialize() {
  if (initialized) {
    // nothing to do, already initialized
    return;
  }

  // Initialize all the static ros members
  nh_ptr_ = std::unique_ptr<ros::NodeHandle>(new ros::NodeHandle);

  // create subscriber
  telemetry_sub_ptr_ = std::unique_ptr<ros::Subscriber>(new ros::Subscriber);
  *telemetry_sub_ptr_ =
      nh_ptr_->subscribe("telemetry", 10, &RobotHelper::callback);

  while (ros::ok()) {
    // Wait for crucial telemetry subscriber to receive messages
    // NOTE: Here, time is wasted waiting. I don't want to do that too often 
    // ...
  }

  // Create publisher
  some_pub_ptr_ = std::unique_ptr<ros::Publisher>(new ros::Publisher);
  *some_pub_ptr_ = nh_ptr_->advertise<std_msgs::Bool>("commands", 10);
}


class PathNavigation : public NavigationBase {
public:
  // For every navigation action I create its own object
  PathNavigation(double x, double y, double z);

  // Actually perform the navigation
  // This has to be called periodically until navigation is complete.
  // In between I could handle obstacle avoidance or perform other actions.
  // Hence this should be a non-blocking function and called periodically
  int execute(bool &navigation_completed);

private:
  static std::unique_ptr<ros::NodeHandle> nh_ptr_;
  static std::unique_ptr<ros::Publisher> some_pub_ptr_;
};

PathNavigation::PathNavigation(double x, double y, double z) {
  // In the constructor I compute the trajectory for example, as this remains
  // valid until the goal is reached.
}

int PathNavigation::execute() {
  // Every class function needs to check the initialization
  // since there is no knowing which function is going to be called first.
  initialize();

  // Publish commands like this
  RobotHelper::sendCommand(...);  // First call will initialize RobotHelper
  
  return 0; // no errors
}


int main(int argc, char **argv) {
  ros::init(argc, argv, "example");

  // Create navigation
  // The end-user does not need to know about RobotHelper. The navigation-
  // objects take care of everything automatically. 
  std::queue<PathNavigation> navigation_queue;

  PathNavigation waypoint1(1.0, 2.0, 0.0);
  navigation_queue.push(waypoint1);

  PathNavigation waypoint2(3.0, 3.0, 0.0);
  navigation_queue.push(waypoint2);

  // Execute navigation
  bool nav_complete = false;
  while (ros::ok() && !navigation_queue.empty()) {
    navigation_queue.front().execute(nav_complete);

    // Check if waypoint reached
    if (nav_complete) {
      // Proceed with next waypoint
      navigation_queue.pop();
    }
  }

  return 0;
}

This approach works fine, but I have the feeling I am wasting resources with the constant if(initialized) checks inside every single one of the library's functions. Is there a common approach to this problem of requiring a static node handle and other things?

EDIT: I expanded the example to make more sense.


Originally posted by potaahto on ROS Answers with karma: 3 on 2017-01-14

Post score: 0


Original comments

Comment by potaahto on 2017-01-15:
@gvdhoom :There are two reasons for the statics. First one is as you suspected, that I don't want to miss messages. So I initialize the subscribers once and wait to receive messages. If I do that for every new navigation action, I'm wasting time waiting for the new subscriber every time.

Comment by potaahto on 2017-01-15:
Global awareness might be the wrong term. Essentially I wanted to always know whether my subscribers are receiving messages after they have been initialized once, statically. In other words, I'm only using static to eliminate the time it would take to re-initialize my publishers and subscribers.

Comment by potaahto on 2017-01-15:
What did you mean by "exposing as action"? This sounds exactly like what I need. Would that be having a separate Node running for these actions and triggering them via service calls for example?

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

The overuse of static here is a red flag that this is not a good way to approach this. Can you elaborate a bit on how static makes things better? If not missing msgs is really important, an async pub/sub pattern might not be suitable here.

int PathNavigation::goTo(double x, double y, double z): if this is a function that (ultimately) actually makes your platform move to x, y, z, then it might make sense to expose that as an Action, instead of a subscriber. That would allow your client & server to synchronise their control flows, making it immediately clear if/when a msg is lost. Additionally, the action server could provide feedback while performing the action (fi: distance travelled or remaining).

This has the advantage, that my library has global awareness and I can prevent many user/robot/real-time errors

Can you also elaborate on this? 'global awareness' is typically not seen as an advantage, as it results in tightly coupled code (low locality of change, implicit assumptions about control flow, no access control, etc).


Edit:

There are two reasons for the statics. First one is as you suspected, that I don't want to miss messages. So I initialize the subscribers once and wait to receive messages. If I do that for every new navigation action, I'm wasting time waiting for the new subscriber every time.

Global awareness might be the wrong term. Essentially I wanted to always know whether my subscribers are receiving messages after they have been initialized once, statically. In other words, I'm only using static to eliminate the time it would take to re-initialize my publishers and subscribers.

I'm slightly confused: why would you want to reinitialise your publishers and subscribers?

You have an object, with a lifetime probably longer than that of a single request. Make use of that by initialising your publishers and subscribers only once (initialise member variables in the ctor fi), and use those instances when handling future requests.

What did you mean by "exposing as action"? This sounds exactly like what I need. Would that be having a separate Node running for these actions and triggering them via service calls for example?

In short: periodic dataflow where a single msg lost is not the end of the world: topics. Short (almost instantaneous) synchronous invocations: services. For long running (a)synchronous invocations that you also would like to monitor and potentially preempt: actions.

For a discussion on the different interaction patterns, please see ROS/Patterns/Communication. The actionlib library implements support for the Action pattern (essentially async services with a future or promise).

Can you give us an example of what a typical flow of control and data is for the functionality that you are wrapping? It sounds a bit like various responsibilites are mixed and that your current mapping of what you have and how you could/would use that in a ROS application are suboptimal. From your PathNavigation example fi I get the impression that you're very much thinking in an imperative way (ie: calling blocking functions or methods, with control flow basically dictated by the top-to-bottom order in which such invocations are encoded in some outer function (main(..), etc). Replicating that in a ROS node never really works very well (although it's possible), and having to resort to tricks like in the OP should make your 'spidey sense' go off.


Originally posted by gvdhoorn with karma: 86574 on 2017-01-15

This answer was ACCEPTED on the original site

Post score: 1


Original comments

Comment by potaahto on 2017-01-15:
Sorry, I expanded my pseudo code now and hopefully it will make more sense. Basically I am using a class for navigations, and for every new navigation I instantiate a new object. That's why I would recreate subscribers and publishers every time.

Comment by potaahto on 2017-01-15:
You are completely right, I do feel I am doing something wrong/unnecessary. That's why I am asking for best practices here. Or maybe example libraries doing something similar (basic navigation).

Comment by gvdhoorn on 2017-01-15:
For one 'example library' that deals with navigation, you could look at move_base. That seems to do at least what you show in your extended example, and a bit more even, but without all the blocking calls, and with a bit more separation of concerns.

Comment by gvdhoorn on 2017-01-15:
As to your current design: you could still initialise your pubs & subs once, if you change your PathNavigation class to be more of an 'executor', which stays alive, and just receives new commands (goals in move_base terminology), instead of being recreated for every waypoint.

Comment by gvdhoorn on 2017-01-15:
So instead of pushing PathNavigation instances, you'd create a single instance, which is your "robot controller" so to say, introduce a data structure to encapsulate just the waypoint info and give your PathNavigation instance a list of waypoints to navigate to.

Comment by gvdhoorn on 2017-01-15:
But that is basically what move_base does.

Comment by potaahto on 2017-01-15:
Thank you very much. I will look into all that you have written and rethink the entire thing.

Comment by gvdhoorn on 2017-01-15:
No problem. Let us know about your progress.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.