Service client node class problem

hello there,

I’m trying to do the service client node for the quiz 4.7 on services.

My service works well when I do

ros2 service call /rotate services_quiz_srv/srv/Spin "{direction: 'right', angular_velocity: 1, time: 3}"

My services gives me back the good behaviour and this message:

user:~/ros2_ws$ ros2 launch services_quiz services_quiz_server.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2023-06-21-16-36-24-289049-4_xterm-1148
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [rotate_server-1]: process started with pid [1149]
[rotate_server-1] [INFO] [1687365401.469843609] [rotate_service]: start rotating right
[rotate_server-1] [INFO] [1687365404.470299481] [rotate_service]: stop rotating right

When I try to do the client in a class I don’t have the same behaviour:

user:~/ros2_ws$ ros2 launch services_quiz services_quiz_server.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2023-06-21-16-45-08-354879-4_xterm-2032
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [rotate_server-1]: process started with pid [2033]
[rotate_server-1] [INFO] [1687365908.995054596] [rotate_service]: start rotating right
[rotate_server-1] [INFO] [1687365911.996097658] [rotate_service]: stop rotating right
[rotate_server-1] [INFO] [1687365911.996305815] [rotate_service]: start rotating right
[rotate_server-1] [INFO] [1687365914.996546838] [rotate_service]: stop rotating right
[rotate_server-1] [INFO] [1687365914.996641613] [rotate_service]: start rotating right
[rotate_server-1] [INFO] [1687365917.996940060] [rotate_service]: stop rotating right
[rotate_server-1] [INFO] [1687365917.997047116] [rotate_service]: start rotating right
[rotate_server-1] [INFO] [1687365920.997297565] [rotate_service]: stop rotating right
[rotate_server-1] [INFO] [1687365920.997418616] [rotate_service]: start rotating right
[rotate_server-1] [INFO] [1687365923.997756524] [rotate_service]: stop rotating right

My code looks like this:

#include "rclcpp/rclcpp.hpp"
#include "rclcpp/timer.hpp"
#include "services_quiz_srv/srv/spin.hpp"

#include <chrono>
#include <cstdlib>
#include <future>
#include <memory>

using namespace std::chrono_literals;
using Spin = services_quiz_srv::srv::Spin;

class ServiceClient : public rclcpp::Node {
private:
  rclcpp::Client<Spin>::SharedPtr client_;
  rclcpp::TimerBase::SharedPtr timer_;
  bool service_done_ = false;

  void timer_callback() {
    RCLCPP_INFO(this->get_logger(), "timer callback");
    while (!client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(
            this->get_logger(),
            "Client interrupted while waiting for service. Terminating...");
        return;
      }
      RCLCPP_INFO(this->get_logger(),
                  "Service Unavailable. Waiting for Service...");
    }
    RCLCPP_INFO(this->get_logger(), "service reached");
    auto request = std::make_shared<Spin::Request>();
    request->direction = "right";
    request->angular_velocity = 1;
    request->time = 3;

    service_done_ = false;
    auto result_future = client_->async_send_request(
        request, std::bind(&ServiceClient::response_callback, this,
                           std::placeholders::_1));

    RCLCPP_INFO(this->get_logger(), "end of timer callback");
  }

  void response_callback(rclcpp::Client<Spin>::SharedFuture future) {
    RCLCPP_INFO(this->get_logger(), "response callback");
    auto status = future.wait_for(1s);
    if (status == std::future_status::ready) {
      RCLCPP_INFO(this->get_logger(), "Result: success");
      service_done_ = true;
    } else {
      RCLCPP_INFO(this->get_logger(), "Service In-Progress...");
    }
    RCLCPP_INFO(this->get_logger(), "end response callback");
  }

public:
  ServiceClient() : Node("rotate_client") {
    RCLCPP_INFO(this->get_logger(), "rotate_client launched");
    client_ = this->create_client<Spin>("rotate");
    timer_ = this->create_wall_timer(
        1s, std::bind(&ServiceClient::timer_callback, this));
  }

  bool is_service_done() const { return this->service_done_; }
};

int main(int argc, char *argv[]) {
  rclcpp::init(argc, argv);

  auto service_client = std::make_shared<ServiceClient>();
  while (!service_client->is_service_done()) {
    rclcpp::spin_some(service_client);
  }

  rclcpp::shutdown();
  return 0;
}

and displays this:

user:~$ ros2 launch services_quiz services_quiz_client.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2023-06-21-16-45-05-194765-4_xterm-2010
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [rotate_client-1]: process started with pid [2011]
[rotate_client-1] [INFO] [1687365905.358862317] [rotate_client]: rotate_client launched
[rotate_client-1] [INFO] [1687365906.360016374] [rotate_client]: timer callback
[rotate_client-1] [INFO] [1687365907.360192368] [rotate_client]: Service Unavailable. Waiting for Service...
[rotate_client-1] [INFO] [1687365908.360392928] [rotate_client]: Service Unavailable. Waiting for Service...
[rotate_client-1] [INFO] [1687365908.865549225] [rotate_client]: service reached
[rotate_client-1] [INFO] [1687365908.865757831] [rotate_client]: end of timer callback
[rotate_client-1] [INFO] [1687365908.865839867] [rotate_client]: timer callback
[rotate_client-1] [INFO] [1687365908.865877264] [rotate_client]: service reached
[rotate_client-1] [INFO] [1687365908.865957374] [rotate_client]: end of timer callback
[rotate_client-1] [INFO] [1687365909.359922030] [rotate_client]: timer callback
[rotate_client-1] [INFO] [1687365909.360033576] [rotate_client]: service reached
[rotate_client-1] [INFO] [1687365909.360135143] [rotate_client]: end of timer callback
[rotate_client-1] [INFO] [1687365910.359893384] [rotate_client]: timer callback
[rotate_client-1] [INFO] [1687365910.359983630] [rotate_client]: service reached
[rotate_client-1] [INFO] [1687365910.360072448] [rotate_client]: end of timer callback
[rotate_client-1] [INFO] [1687365911.359869095] [rotate_client]: timer callback
[rotate_client-1] [INFO] [1687365911.359959513] [rotate_client]: service reached
[rotate_client-1] [INFO] [1687365911.360050950] [rotate_client]: end of timer callback
[rotate_client-1] [INFO] [1687365911.996393542] [rotate_client]: response callback
[rotate_client-1] [INFO] [1687365911.996450567] [rotate_client]: Result: success
[rotate_client-1] [INFO] [1687365911.996471368] [rotate_client]: end response callback
[INFO] [rotate_client-1]: process has finished cleanly [pid 2011]

The robot starts rotating at the first “service reached”. I never get to see “Service In-Progress…”.
It seems that while the robot executes the first service it starts a new timer callback…

I don’t understand what I did wrong. My code is inspired by the service client code using node inheritance of the course.

Thank you in advance for your help.
Pablo

Hi @pabled91 ,

Welcome to this Community!

I see that you have tried to implement the Service Client logic from my post : ROS2 Service Client in C++ with Classes with Node Inheritance.

The problem is that the way you have implemented it is wrong.
The timer callback will keep calling the service after a certain time. The best way to avoid this is to wait for the amount of duration until the timer finishes. But unfortunately, you cannot change the timeout in the timer callback dynamically. (You can but it is a big process not suitable for beginners.)

The simpler method is to not use the timer. The steps to do that will be as follows:

  1. Move the wait_for_service code to the constructor.
  2. Move the async_send_request part to a function call.
  3. Call the function call to initialize a service in the main function.

Here is the altered version [WARNING: NOT TESTED]:

#include "rclcpp/rclcpp.hpp"
#include "rclcpp/timer.hpp"
#include "services_quiz_srv/srv/spin.hpp"

#include <chrono>
#include <cstdlib>
#include <future>
#include <memory>

using namespace std::chrono_literals;
using Spin = services_quiz_srv::srv::Spin;

class ServiceClient : public rclcpp::Node {
private:
  rclcpp::Client<Spin>::SharedPtr client_;
  rclcpp::TimerBase::SharedPtr timer_;
  bool service_done_ = false;

  void response_callback(rclcpp::Client<Spin>::SharedFuture future) {
    RCLCPP_INFO(this->get_logger(), "response callback");
    auto status = future.wait_for(1s);
    if (status == std::future_status::ready) {
      RCLCPP_INFO(this->get_logger(), "Result: success");
      service_done_ = true;
    } else {
      RCLCPP_INFO(this->get_logger(), "Service In-Progress...");
    }
    RCLCPP_INFO(this->get_logger(), "end response callback");
  }

public:
  ServiceClient() : Node("rotate_client") {
    RCLCPP_INFO(this->get_logger(), "rotate_client launched");
    client_ = this->create_client<Spin>("rotate");

    while (!client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(
            this->get_logger(),
            "Client interrupted while waiting for service. Terminating...");
        return;
      }
      RCLCPP_INFO(this->get_logger(),
                  "Service Unavailable. Waiting for Service...");
    }
    RCLCPP_INFO(this->get_logger(), "service reached");
  }

  void call_service(std::string direction, float ang_vel, int time) {
  	auto request = std::make_shared<Spin::Request>();
    request->direction = direction;
    request->angular_velocity = ang_vel;
    request->time = time;

    service_done_ = false;
    auto result_future = client_->async_send_request(
        request, std::bind(&ServiceClient::response_callback, this,
                           std::placeholders::_1));
  }

  bool is_service_done() const { return this->service_done_; }
};

int main(int argc, char *argv[]) {
  rclcpp::init(argc, argv);

  auto service_client = std::make_shared<ServiceClient>();
  service_client->call_service();
  while (!service_client->is_service_done()) {
    rclcpp::spin_some(service_client);
  }

  rclcpp::shutdown();
  return 0;
}

// End of Code

Here is Tip: Don’t just copy-paste the code and change a few lines hoping you can adapt the code to your functionality. It will never work that way. Understand the logic flow and then make changes to suit your logic.

Regards,
Girish

1 Like

Hello,

thank you @girishkumar.kannan for your help.

I tried to implement your logic because it was added in the service course (unit 4). I’m trying to put all my nodes in classes to avoid putting it all in the main.

When I did the example in the course I didn’t see any problem. Maybe because the execution is short enough to dont let time for another timer callback.

It works now, I think I understand now thanks to your help. I just have a concern: it seems that the part of response callback saying that the service is in progress is never reached, because the response callback is only executed once the service ended.

How could we now that the service is in progress from the client side.

Again thank you for your help.

user:~/ros2_ws$ ros2 launch services_quiz services_quiz_client.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2023-06-22-09-04-06-495352-3_xterm-14852
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [rotate_client-1]: process started with pid [14853]
[rotate_client-1] [INFO] [1687424646.690291784] [rotate_client]: rotate_client launched
[rotate_client-1] [INFO] [1687424646.691614330] [rotate_client]: service reached
[rotate_client-1] [INFO] [1687424646.691660807] [rotate_client]: calling service
[rotate_client-1] [INFO] [1687424651.693192236] [rotate_client]: response callback
[rotate_client-1] [INFO] [1687424651.693255160] [rotate_client]: Result: success
[rotate_client-1] [INFO] [1687424651.693285092] [rotate_client]: end response callback
[INFO] [rotate_client-1]: process has finished cleanly [pid 14853]
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/timer.hpp"
#include "services_quiz_srv/srv/spin.hpp"

#include <chrono>
#include <cstdlib>
#include <future>
#include <memory>

using namespace std::chrono_literals;
using Spin = services_quiz_srv::srv::Spin;

class ServiceClient : public rclcpp::Node {
private:
  rclcpp::Client<Spin>::SharedPtr client_;
  rclcpp::TimerBase::SharedPtr timer_;
  bool service_done_ = false;

  void response_callback(rclcpp::Client<Spin>::SharedFuture future) {
    RCLCPP_INFO(this->get_logger(), "response callback");
    auto status = future.wait_for(1s);
    if (status == std::future_status::ready) {
      RCLCPP_INFO(this->get_logger(), "Result: success");
      service_done_ = true;
    } else {
      RCLCPP_INFO(this->get_logger(), "Service In-Progress...");
    }
    RCLCPP_INFO(this->get_logger(), "end response callback");
  }

public:
  ServiceClient() : Node("rotate_client") {
    RCLCPP_INFO(this->get_logger(), "rotate_client launched");
    client_ = this->create_client<Spin>("rotate");
    while (!client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(
            this->get_logger(),
            "Client interrupted while waiting for service. Terminating...");
        return;
      }
      RCLCPP_INFO(this->get_logger(),
                  "Service Unavailable. Waiting for Service...");
    }
    RCLCPP_INFO(this->get_logger(), "service reached");
  }

  void call_service(std::string direction, float angular_velocity, int time) {
    RCLCPP_INFO(this->get_logger(), "calling service");
    auto request = std::make_shared<Spin::Request>();
    request->direction = direction;
    request->angular_velocity = angular_velocity;
    request->time = time;

    service_done_ = false;
    auto result_future = client_->async_send_request(
        request, std::bind(&ServiceClient::response_callback, this,
                           std::placeholders::_1));
  }

  bool is_service_done() const { return this->service_done_; }
};

int main(int argc, char *argv[]) {
  rclcpp::init(argc, argv);

  auto service_client = std::make_shared<ServiceClient>();
  service_client->call_service("right", 1, 5);
  while (!service_client->is_service_done()) {
    rclcpp::spin_some(service_client);
  }

  rclcpp::shutdown();
  return 0;
}

Hi @pabled91 ,

Glad to know it worked for you!

Yes, most of the times, the "Service In-Progress..." will never be printed.
The reason is that this response_callback will be called only once by the service client per service call.
If you were to have this response_callback running as a loop, then you will see the Service In-Progress... message.
For example, if you call the service in the main function and instead of using the response_callback you use a while loop within the main function, then you will might see the message that service is in progress.

But to know answer your question, How to know if service is in progress?, You must create another thread to check if the future_status is not ready. Services are meant to be run async to avoid this “blocking” effect. Therefore, when the service is in progress, the message will not be printed.
I also believe that the status is set to ready immediately in the future by ROS2, so any state in between is not reported, which also might be the reason why the else block never gets executed (which would display service in-progress).

I hope that clarified your doubts.

Regards,
Girish

1 Like

Thank you for your help.
Maybe I’ll experiment it later.

Regards.

Pablo

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.