(ROS2-4 시작) 서비스

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를 진행하면서 결국에는 매우 명백해질 것입니다.

이제 노드를 만들고 서로 통신할 수 있습니다.

그러나 지금까지는 기존 메시지만 사용했습니다.

주제 및 서비스에 대해 다른 메시지 유형을 사용해야 하는 경우에는 어떻게 합니까?

이 경우 다음 섹션에서 볼 수 있는 고유한 메시지 유형을 빌드해야 합니다.