ROS2 Service
- A ROS2 Service is a client/server system
- Synchronous or asyncronous
- One Message type for Request, one message type for Response
- Can be written in python, c++, … directly inside ROS nodes
- A service server can only exist once, but can have many clients
- Example of service interface
ros2 interface show example_interfaces/srv/AddTwoInts
"
int64 a # request
int64 b # request
---
int64 sum # response
"
Server
In Python
cd ~/ros2_ws/src/my_py_pkg/my_py_pkg
touch add_two_ints_server.py
chmod +x add_two_ints_server.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddTwoIntsServerNode(Node):
def __init__(self):
super().__init__("add_two_ints_server")
self.server_ = self.create_service(
AddTwoInts, "add_two_ints", self.callback_add_two_ints)
self.get_logger().info("Add two ints server has been started.")
def callback_add_two_ints(self, request, response):
response.sum = request.a + request.b
self.get_logger().info(str(request.a) + " + " +
str(request.b) + " = " + str(response.sum))
return response
def main(args=None):
rclpy.init(args=args)
node = AddTwoIntsServerNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
- command about service
ros2 service list # you can see list of services
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 10}"
In C++
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
using std::placeholders::_1;
using std::placeholders::_2;
class AddTwoIntsServerNode : public rclcpp::Node
{
public:
AddTwoIntsServerNode() : Node("add_two_ints_server")
{
server_ = this->create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints",
std::bind(&AddTwoIntsServerNode::callbackAddTwoInts, this, _1, _2));
RCLCPP_INFO(this->get_logger(), "Service server has been started.");
}
private:
void callbackAddTwoInts(const example_interfaces::srv::AddTwoInts::Request::SharedPtr request,
const example_interfaces::srv::AddTwoInts::Response::SharedPtr response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(this->get_logger(), "%d + %d = %d", request->a, request->b, response->sum);
}
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr server_;
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<AddTwoIntsServerNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Client without OOP
In Python
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
def main(args=None):
rclpy.init(args=args)
node = Node("add_two_ints_no_oop")
client = node.create_client(AddTwoInts, "add_two_ints")
while not client.wait_for_service(1.0):
node.get_logger().warn("Waiting for Server Add Two Ints...")
request = AddTwoInts.Request()
request.a = 3
request.b = 8
future = client.call_async(request)
rclpy.spin_until_future_complete(node, future)
try:
response = future.result()
node.get_logger().info(str(request.a) + " + " +
str(request.b) + " = " + str(response.sum))
except Exception as e:
node.get_logger().error("Service call failed %r" % (e,))
rclpy.shutdown()
if __name__ == "__main__":
main()
client.call is a synchoronous call and that will block until the response is given by the server, but it is not recommended because in some situations it may be stuck forever, even if the response is given.
We will always use client.call_async and when you use it will call_async it will send a request, but it will continue to be executed
In C++
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("add_two_ints_client_no_oop");
auto client = node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
while (!client->wait_for_service(std::chrono::seconds(1)))
{
RCLCPP_WARN(node->get_logger(), "Waiting for the server to be up...");
}
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = 3;
request->b = 8;
auto future = client->async_send_request(request);
if (rclcpp::spin_until_future_complete(node, future) == rclcpp::executor::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(node->get_logger(), "%d + %d = %d", request->a, request->b, future.get()->sum);
}
else {
RCLCPP_ERROR(node->get_logger(), "Error while calling service");
}
rclcpp::shutdown();
return 0;
}
Client with OOP
In Python
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from functools import partial
from example_interfaces.srv import AddTwoInts
class AddTwoIntsClientNode(Node):
def __init__(self):
super().__init__("add_two_ints_client")
self.call_add_two_ints_server(6, 7)
def call_add_two_ints_server(self, a, b):
client = self.create_client(AddTwoInts, "add_two_ints")
while not client.wait_for_service(1.0):
self.get_logger().warn("Waiting for Server Add Two Ints...")
request = AddTwoInts.Request()
request.a = a
request.b = b
future = client.call_async(request)
future.add_done_callback(partial(self.callback_call_add_two_ints, a=a, b=b))
def callback_call_add_two_ints(self, future, a, b):
try:
response = future.result()
self.get_logger().info(str(a) + " + " +
str(b) + " = " + str(response.sum))
except Exception as e:
self.get_logger().error("Service call failed %r" % (e,))
def main(args=None):
rclpy.init(args=args)
node = AddTwoIntsClientNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
you shouldn’t call another spin functionality once you call spin within the node.
We can call spin_until_future_complete, because we already call spin in main function.
In C++
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
class AddTwoIntsClientNode : public rclcpp::Node
{
public:
AddTwoIntsClientNode() : Node("add_two_ints_client")
{
// thread1_ = std::thread(std::bind(&AddTwoIntsClientNode::callAddTwoIntsService, this, 1, 4));
threads_.push_back(std::thread(std::bind(&AddTwoIntsClientNode::callAddTwoIntsService, this, 1, 4)));
threads_.push_back(std::thread(std::bind(&AddTwoIntsClientNode::callAddTwoIntsService, this, 2, 8)));
}
void callAddTwoIntsService(int a, int b)
{
auto client = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
while (!client->wait_for_service(std::chrono::seconds(1)))
{
RCLCPP_WARN(this->get_logger(), "Waiting for the server to be up...");
}
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = a;
request->b = b;
auto future = client->async_send_request(request);
try{
auto response = future.get();
RCLCPP_INFO(this->get_logger(), "%d + %d = %d", a, b, response->sum);
}
catch(const std::exception &e)
{
RCLCPP_ERROR(this->get_logger(), "Service call failed");
}
}
private:
// std::thread thread1_; // it allocates thread one by one
std::vector<std::thread> threads_; // it makes thread pool, so can allocate many thread without initializing
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<AddTwoIntsClientNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
we call spin in main function. And we are not possible to call spin in other function more.
In callAddTwoIntsService, future.get() will blocks this thread. That means that the function will not exit and the constructor will not exit. Then rclcpp::spin(node) will not be executed. In order to execute spin, we need to start it in a different thread.
Debug Services ROS2 tools
we can see service list
ros2 service list
"
/add_two_ints
/add_two_ints_server/describe_parameters
/add_two_ints_server/get_parameter_types
/add_two_ints_server/get_parameters
/add_two_ints_server/list_parameters
/add_two_ints_server/set_parameters
/add_two_ints_server/set_parameters_atomically
"
we can know interfaces of the service
ros2 node list
# /add_two_ints_server
ros2 node info /add_two_ints_server
"
/add_two_ints_server
Subscribers:
/parameter_events: rcl_interfaces/msg/ParameterEvent
Publishers:
/parameter_events: rcl_interfaces/msg/ParameterEvent
/rosout: rcl_interfaces/msg/Log
Service Servers:
**/add_two_ints: example_interfaces/srv/AddTwoInts <-- you can find it !**
/add_two_ints_server/describe_parameters: rcl_interfaces/srv/DescribeParameters
/add_two_ints_server/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
/add_two_ints_server/get_parameters: rcl_interfaces/srv/GetParameters
/add_two_ints_server/list_parameters: rcl_interfaces/srv/ListParameters
/add_two_ints_server/set_parameters: rcl_interfaces/srv/SetParameters
/add_two_ints_server/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
Service Clients:
Action Servers:
Action Clients:
"
# or
ros2 service type /add_two_ints
# example_interfaces/srv/AddTwoInts
ros2 interface show example_interfaces/srv/AddTwoInts
"
int64 a
int64 b
---
int64 sum
"
call service in command line
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 5}"
#or
rqt
you can use this service by inputing data in Expression and press Call button
remap topic name of a service
ros2 run my_cpp_pkg add_two_ints_server
ros2 service list
"
/add_two_ints
/add_two_ints_server/describe_parameters
/add_two_ints_server/get_parameter_types
/add_two_ints_server/get_parameters
/add_two_ints_server/list_parameters
/add_two_ints_server/set_parameters
/add_two_ints_server/set_parameters_atomically
"
ros2 run my_cpp_pkg add_two_ints_server --ros-args -r add_two_ints:=new_name
ros2 service list
"
/add_two_ints_server/describe_parameters
/add_two_ints_server/get_parameter_types
/add_two_ints_server/get_parameters
/add_two_ints_server/list_parameters
/add_two_ints_server/set_parameters
/add_two_ints_server/set_parameters_atomically
/new_name
"
for communicating server and client, you must match the topic name of service
# it can't work because of different topic name of service
ros2 run my_cpp_pkg add_two_ints_client
# so you must match the topic name of service of server and client
ros2 run my_cpp_pkg add_two_ints_client --ros-args -r add_two_ints:=new_name
Activity
Let’s practice with ROS2 Services!
You will start from the Topic activity you did in the last section on Topics.
Here is what you got:
Quick recap:
- The node “number_publisher” publishes a number on the /”number” topic.
- The node “number_counter” gets the number, adds it to a counter, and publishes the counter on the “/number_count” topic.
And now, here is what you’ll add:
Add a functionality to reset the counter to zero:
- Create a service server inside the “number_counter” node.
- Service name: “/reset_counter”
- Service type: example_interfaces/srv/SetBool. Use “ros2 interface show” to discover what’s inside!
- When the server is called, you check the boolean data from the request. If true, you set the counter variable to 0.
We will then call the service directly from the command line. You can also decide - for more practice - to create your own custom node to call this “/reset_counter” service.
And of course, as always, you can do the activity for both the Python and Cpp versions.
I’ll see you in the next lecture for the solution.
Solution
number_counter.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.msg import Int64
from example_interfaces.srv import SetBool
class NumberCounterNode(Node):
def __init__(self):
super().__init__("number_counter")
self.counter_ = 0
self.number_count_publisher_ = self.create_publisher(
Int64, "number_count", 10)
self.number_subscriber_ = self.create_subscription(
Int64, "number", self.callback_number, 10)
self.reset_counter_service_ = self.create_service(
SetBool, "reset_counter", self.callback_reset_counter)
self.get_logger().info("Number Counter has been started.")
def callback_number(self, msg):
self.counter_ += msg.data
new_msg = Int64()
new_msg.data = self.counter_
self.number_count_publisher_.publish(new_msg)
def callback_reset_counter(self, request, response):
if request.data:
self.counter_ = 0
response.success = True
response.message = "Counter has been reset"
else:
response.success = False
response.message = "Counter has not been reset"
return response
def main(args=None):
rclpy.init(args=args)
node = NumberCounterNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
number_count.cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/msg/int64.hpp"
#include "example_interfaces/srv/set_bool.hpp"
using std::placeholders::_1;
using std::placeholders::_2;
class NumberCounterNode : public rclcpp::Node
{
public:
NumberCounterNode() : Node("number_counter"), counter_(0)
{
counter_publisher_ = this->create_publisher<example_interfaces::msg::Int64>("number_count", 10);
number_subscriber_ = this->create_subscription<example_interfaces::msg::Int64>(
"number", 10, std::bind(&NumberCounterNode::callbackNumber, this, std::placeholders::_1));
reset_number_service_ = this->create_service<example_interfaces::srv::SetBool>(
"reset_counter",
std::bind(&NumberCounterNode::callbackResetCounter, this, _1, _2));
RCLCPP_INFO(this->get_logger(), "Number Counter has been started.");
}
private:
void callbackNumber(const example_interfaces::msg::Int64::SharedPtr msg)
{
counter_ += msg->data;
auto newMsg = example_interfaces::msg::Int64();
newMsg.data = counter_;
counter_publisher_->publish(newMsg);
}
void callbackResetCounter(const example_interfaces::srv::SetBool::Request::SharedPtr request,
const example_interfaces::srv::SetBool::Response::SharedPtr response)
{
if (request->data)
{
counter_ = 0;
response->success = true;
response->message = "Counter has been reset";
}
else
{
response->success = false;
response->message = "Counter has not been reset";
}
}
int counter_;
rclcpp::Publisher<example_interfaces::msg::Int64>::SharedPtr counter_publisher_;
rclcpp::Subscription<example_interfaces::msg::Int64>::SharedPtr number_subscriber_;
rclcpp::Service<example_interfaces::srv::SetBool>::SharedPtr reset_number_service_;
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<NumberCounterNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Section Conclusion
In this section you have discovered ROS2 Services, and seen how you can use them to add client/server communications between your nodes.
To recap, Services are:
- Used for client/server types of communication.
- Synchronous or asynchronous (though it’s recommended to use them asynchronously, you decide to wait after in the thread).
- Anonymous: a client does not know which node is behind the service, it just calls the service. And the server does not know which nodes are clients, it just receives requests and responds to them.
To implement Services inside your nodes:
- Create a node or start from an existing one. Add as many Service servers as you want (all with different names)
- When you call a Service server from a Service client, make sure that the Service name, as well as the Service type (request + response) are identical.
- You can only create one server for a Service, but you can create many clients.
When you want to add a new communication between your nodes, ask yourself this question: “Am I just sending some data, or do I expect a response after I send the message?”. This will tell you if you need to use a Topic or a Service. And, as you progress with ROS2, it will eventually become quite obvious for you.
So, now you can create nodes and make them communicate between each other. But, you’ve only used existing messages so far. What if you need to use other message types for your Topics and Services?
Well, in this case, you’ll need to build your own message types, and that’s what we’ll see in the next section.
'Robotics > ROS2' 카테고리의 다른 글
[ROS2 시작하기 - 6] Parameter (0) | 2023.03.01 |
---|---|
[ROS2 시작하기 - 5] Interface (0) | 2023.02.27 |
[ROS2 시작하기 - 3] Topic (0) | 2023.02.23 |
[ROS2 시작하기 - 2] Tools (0) | 2023.02.21 |
[ROS2 시작하기 - 1] Node (0) | 2023.02.20 |