ROS2 서비스
- ROS2 서비스는 클라이언트/서버 시스템입니다.
- 동기식 또는 비동기식
- 요청에 대한 하나의 메시지 유형, 응답에 대한 하나의 메시지 유형
- Python, C++, …의 ROS 노드에서 직접 작성할 수 있습니다.
- 서비스 서버는 한 번만 존재할 수 있지만 여러 클라이언트를 가질 수 있습니다.
- 서비스 인터페이스 예
ros2 interface show example_interfaces/srv/AddTwoInts
"
int64 a # request
int64 b # request
---
int64 sum # response
"
섬기는 사람
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()
- 서비스에 대한 명령
ros2 service list # you can see list of services
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 10}"
OOP가 없는 클라이언트
#!
/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_async 사용하면 호출_async 요청을 보내지만 계속 실행됩니다.
객체 지향 클라이언트
#!
/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()
스핀이 노드 내에서 호출되면 다른 스핀 함수를 호출해서는 안 됩니다.
main 함수에서 이미 spin을 호출했기 때문에 spin_until_future_complete를 호출할 수 있습니다.
디버그 서비스 ROS2 도구
서비스 일람을 볼 수 있습니다
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 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
"
명령줄에서 서비스 호출
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 5}"
#or
rqt

식에 데이터를 입력하고 통화버튼을 누르면 본 서비스를 이용하실 수 있습니다.
리매핑 서비스의 주제 이름
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
"
서버와 클라이언트가 통신하며 서비스 주제 이름이 일치해야 합니다.
# 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
활동
ROS2 서비스로 연습하자!
이전 섹션의 주제에서 수행한 주제 활동부터 시작합니다.
다음과 같은 결과를 얻을 수 있습니다.
빠른 요약:
- “number_publisher” 노드는 주제 /”number”에 번호를 게시합니다.
- “number_counter” 노드는 번호를 가져와 카운터에 추가하고 “/number_count” 항목에 카운터를 게시합니다.
이제 추가할 내용은 다음과 같습니다.
카운터를 0으로 재설정하는 기능을 추가합니다.
- “number_counter” 노드 내부에 서비스 서버를 생성합니다.
- 서비스 이름: “/reset_counter”
- 서비스 유형: example_interfaces/srv/SetBool. “ros2 interface show”를 사용하여 내부에 무엇이 있는지 알아보십시오!
- 서버를 호출할 때 요청에서 부울 데이터를 확인합니다.
true인 경우 카운터 변수를 0으로 설정합니다.
그런 다음 명령줄에서 직접 서비스를 호출합니다.
또한 (연습을 위해) 이 “/reset_counter” 서비스를 호출하기 위해 고유한 사용자 지정 노드를 만들기로 결정할 수도 있습니다.
물론 평소와 같이 Python 및 Cpp 버전 모두에서 이 작업을 수행할 수 있습니다.
다음 수업에서 만나요, 솔루션.
해결책
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;
}
부분적인 결론
이 섹션에서는 ROS2 서비스를 발견하고 이를 사용하여 노드 간에 클라이언트/서버 통신을 추가하는 방법을 배웠습니다.
요약하면 서비스는 다음과 같습니다.
- 클라이언트/서버 유형 통신용.
- 동기식 또는 비동기식(스레드에서 대기하기로 결정한 경우에도 비동기식으로 사용하는 것이 권장됨).
- 익명: 클라이언트는 어떤 노드가 서비스 뒤에 있는지 알지 못하고 서비스를 호출합니다.
그리고 서버는 어떤 노드가 클라이언트인지 알지 못하며 요청을 수신하고 이에 응답할 뿐입니다.
노드 내부에 서비스를 구현하려면:
- 노드를 생성하거나 기존 노드로 시작하십시오. 원하는 만큼 서비스 서버 추가(모두 다른 이름 사용)
- 서비스 클라이언트에서 서비스 서버를 호출할 때 서비스 이름과 서비스 유형(요청 + 응답)이 동일한지 확인하십시오.
- 서비스에 대해 하나의 서버만 만들 수 있지만 여러 클라이언트를 만들 수 있습니다.
노드 사이에 새로운 통신을 추가하고 싶을 때 스스로에게 다음과 같은 질문을 해보세요. 테마나 서비스를 사용해야 하는지 알려줍니다.
그리고 ROS2를 진행하면서 결국에는 매우 명백해질 것입니다.
이제 노드를 만들고 서로 통신할 수 있습니다.
그러나 지금까지는 기존 메시지만 사용했습니다.
주제 및 서비스에 대해 다른 메시지 유형을 사용해야 하는 경우에는 어떻게 합니까?
이 경우 다음 섹션에서 볼 수 있는 고유한 메시지 유형을 빌드해야 합니다.