<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>i_robo_u.log</title>
        <link>https://velog.io/</link>
        <description>지식이 현실이 되는 공간</description>
        <lastBuildDate>Tue, 12 Mar 2024 07:59:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>i_robo_u.log</title>
            <url>https://velog.velcdn.com/images/i_robo_u/profile/4693b9b5-5af3-4e81-9a7b-5eee8e3a53ec/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. i_robo_u.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/i_robo_u" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[내 로봇의 에너지를 좀 더 효율적으로! 기능 껐다 켰다 하는 Lifecycle Node구현하기(C++)]]></title>
            <link>https://velog.io/@i_robo_u/%EB%A7%88-ROS2-Node-%EA%BB%90%EB%8B%A4-%EC%BC%B0%EB%8B%A4-%ED%95%A0%EC%88%98-%EC%9E%88%EB%82%98-Lifecycle-Node%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0C</link>
            <guid>https://velog.io/@i_robo_u/%EB%A7%88-ROS2-Node-%EA%BB%90%EB%8B%A4-%EC%BC%B0%EB%8B%A4-%ED%95%A0%EC%88%98-%EC%9E%88%EB%82%98-Lifecycle-Node%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0C</guid>
            <pubDate>Tue, 12 Mar 2024 07:59:32 GMT</pubDate>
            <description><![CDATA[<h3 id="동영상-튜토리얼-part1"><strong><a href="https://youtu.be/nH7dMf_2uEs">동영상 튜토리얼 part1</a></strong></h3>
<h3 id="해당-tutorial-code"><strong><a href="https://github.com/IROBOU/simple_lifecycle_pkg">해당 tutorial code</a></strong></h3>
<hr>
<h1 id="배경지식--왜-필요할까">배경지식 &amp; 왜 필요할까?</h1>
<ul>
<li><p>우리는 방이 어두울 때 조명을 킨다. 조명은 우리의 시야를 밝혀주는 기능을 하고 있는 셈이다. </p>
</li>
<li><p>그런데 우리가 외출을 한다고해보자. 우리는 밖에 있는 동안엔 조명의 기능이 필요 없기 때문에 불을 끄고 외출 할 것이다. </p>
</li>
<li><p>그리고 다시 저녁에 돌아오면 조명을 다시 켠다. </p>
</li>
<li><p>이렇듯 로봇에도 수많은 기능들(주행, 주변 지도 그리기, 위치 찾기, 주변 인식하기, 장애물 피하기 등등) 들이 있지만 이 모든 기능들이 항상 필요한 건 아니다. </p>
</li>
<li><p>예를 들어 삼성 로봇 청소기를 생각해보자. </p>
</li>
<li><p><a href="https://youtu.be/PM1yO5aB-p0?feature=shared">영상</a>에서 와 같이 로봇은 주변의 파악하는 기능이 필요하다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/61664159-108f-4a4f-90ce-ab7d477f8c11/image.png" alt=""></p>
</li>
<li><p>이 기능은 지도를 그리는 것인데 어떻게 지도가 그려지는지 참고하고 싶은 사람은 이 <a href="https://youtu.be/34n1tF5OtQU?feature=shared">영상</a>을 보기바란다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/1393d4b5-cce8-4ab5-a0ef-66538fca75bb/image.png" alt=""></p>
</li>
<li><p>이렇게 로봇이 지도를 그리는 과정은 청소를 하기 전에 한 번만 필요하다.</p>
</li>
<li><p>그렇기 때문에 한 번 지도를 작성하고 나선 그 기능을 담당하는 node를 꺼줘야한다. </p>
</li>
<li><p>그래야 로봇의 에너지를 절약할 수 있기 때문이다. </p>
</li>
<li><p>오늘은 이렇듯 특정 기능을 담당하는 node의 상태(꺼졌다, 켜졌다 등)를 정의하고 그 상태를 바꿀 수(껐다, 켰다 등) 있는 lifecycle node에 대해서 배우고 구현해보도록 하자.  </p>
</li>
</ul>
<hr>
<h1 id="용어-설명">&lt;용어 설명&gt;</h1>
<ul>
<li><p>State machine</p>
<ul>
<li>소프트웨어 공학 등에서 시스템이나 프로그램을 묘사하는데 사용하는 개념이다. </li>
<li>한 시스템을 state machine으로 나타낼 때는 그 시스템이 몇 가지 상태가 있다고 가정한다. </li>
<li>이 상태들은 서로간 전이(변화, transition)이 가능하다. </li>
<li>가장 간단한 예시로 지도그리는 기능을 담당하는 node(system)을 생각해보자. </li>
<li>그리고 이 시스템이 1. 꺼진 상태, 2. 켜진 상태를 갖는다고 하자. </li>
<li>그럼 이시스템은 1.꺼진 상태에서 켜진 상태로의 변화, 2.켜진 상태에서 꺼진 상태로의 변화가 일어 날 수 있다. </li>
<li>이렇게 한 시스템의 가능한 상태와 변화를 정의해 묘사한 것을 state machine이라고 한다. </li>
</ul>
</li>
<li><p>Lifecycle</p>
<ul>
<li><p>state machine으로 묘사한 시스템의 상태를 관리하고 제어하는 기능을 제공하는 ROS2의 프레임워크(기능과 도구)이다. </p>
</li>
<li><p>위에서 들었던 지도 그리는 node를 생각하면 상태가 1.꺼짐 2. 켜짐이 있었다. </p>
</li>
<li><p>ROS2의 lifecycle을 이용하면 해당 node가 꺼져있는지, 켜져있는지 상태를 확인 할 수 있다. </p>
</li>
<li><p>또한 현재 상태를 확인한 후 원하는 상태(꺼져있다면 켜고, 켜져있다면 끄는 등)로 변경시킬 수 있다. </p>
</li>
<li><p>하단의 그림은 ROS2에서 lifecycle 기능으로 관리 및 제어 가능한 node의 state machine이다. 뭐가 많아 보이는데 끄고 켜는걸 좀 더 세세하게 나눴을 뿐 본질은 어렵지 않다. 하나하나 살펴보자. 참고로 안외워도 되고 구현해야되는데 생각 안날 때 밑에 그림 다시 찾아서 천천히 구현하면 된다.   <img src="https://velog.velcdn.com/images/i_robo_u/post/ba104139-6fc3-41c7-82bb-14882e906fbb/image.png" alt=""></p>
</li>
<li><p>node 상태를 나타내는 용어</p>
<ul>
<li>Unconfigured : node가 객체화(instantiated)된 직후의 상태이다. node class의 생성자가 호출됐다고 생각하면된다. 이 상태에서는 변수등이 선언만 형식적으로 되어 있을 뿐 의미있는 데이터를 저장하지 않는 것이 일반적이다. 이 상태의 특징을 생각하면서 구현하도록 하자. <ul>
<li>Unconfigured에서 변경 가능한 상태<ul>
<li>&quot;configure&quot;를 통해서 &quot;Inactive&quot; 상태로 변경 가능</li>
<li>&quot;shutdown&quot;을 통해서 &quot;Finalized&quot; 상태로 변경 가능 </li>
</ul>
</li>
</ul>
</li>
<li>Inactive : node내 parameter를 변경하거나, 특정 topic의 publication/subscription을 생성/삭제 하거나, service를 생성/삭제 하는 등 system구성이 이루어진 상태이다. 다만 생성/삭제만 됐을 뿐 실제로 데이터를 구독하거나 service를 처리하진 않는 상태이다. 이 상태의 특징을 생각하면서 구현하도록 하자.<ul>
<li>Inactive에서 변경 가능한 상태<ul>
<li>&quot;shutdown&quot;을 통해서 &quot;Finalized&quot; 상태로 변경 가능 </li>
<li>&quot;cleanup&quot;을 통해서 &quot;Unconfigured&quot; 상태로 변경 가능</li>
<li>&quot;activate&quot;를 통해서 &quot;Active&quot; 상태로 변경 가능 </li>
</ul>
</li>
</ul>
</li>
<li>Active : 실제로 node가 기능을 수행할 때 상태이다. 기능을 수행하기 위해 데이터를 읽고 처리하거나, service 요청을 처리, output을 만들어낸다. 다시 말하면 Inactive 상태에서 생성되어 있던 publisher, subscription, service등이 제 역할을 하기 시작한다는 말이다. <ul>
<li>Active에서 변경 가능한 상태<ul>
<li>&quot;deactivate&quot;를 통해서 &quot;Inactive&quot; 상태로 변경 가능</li>
<li>&quot;shutdown&quot;을 통해서 &quot;Finalized&quot; 상태로 변경 가능 </li>
</ul>
</li>
</ul>
</li>
<li>Finalized : node가 완전히 없어지기 직전 상태이다. node를 바로 없애면 되지 왜 있나 생각할 수 있다. 이 상태는 &quot;shutdown&quot;을 통해서 도달 할 수도 있지만, error가 발생해서도 도달 할 수 있는 상태로써 error등을 잡는 debugging용도로 사용되기 위한 상태이다. 일단 이런 상태가 있구나하고 참고하도록 하자. <ul>
<li>Finalized에서 변경 가능한 상태<ul>
<li>&quot;destroy&quot;를 통해서 node를 완전히 없앨 수 있다.(node에 할당된 모든 메모리가 해제된다) </li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>상태를 변화시키는 행위를 나타내는 용어</p>
<ul>
<li>create : 단지 node의 생성자(constructor)만 실행한다. </li>
<li>configure : parameter, publication/subscription, service server/client등을 생성한다. 실제 데이터 처리나 서비스 요청에 대한 응답은 처리하지 않는다. 관련 구현 함수: on_configure() </li>
<li>cleanup : configure에서 생성했던 변수 및 데이터를 지운다. 보통 re configure하기 전에 호출한다. 관련 구현 함수: on_cleanup</li>
<li>activate : configure에서 생성했던 publisher, subscriber, service server등이 실제로 기능을 수행한다. 관련 구현 함수: on_activate</li>
<li>deactivate : activate에서 수행되던 기능을 멈춘다. node가 없어지는 것은 아니다. 관련 구현 함수: on_deactivate </li>
<li>shutdown : node의 메모리가 해지되기 전(파괴되기 전) 필요한 메모리 해제를 추가로 진행하는 과정이다. 구현할 때 cleanup과 다소 유사한 성격을 지니는 경우가 있다. 다만 cleanup과 다르게 unconfigured, inactive, active상태에서 사용자가 node를 종료 시키고 싶을 때 호출 될 수 있다.  </li>
<li>destroy : node 자체의 메모리를 해지하는 행위이다. </li>
</ul>
</li>
<li><p>상태 변화동안을 묘사하는 용어</p>
<ul>
<li>Configuring : Unconfigured 상태에서 Inactive 상태로 전환 중인 상태를 나타내는 용어이다. </li>
<li>CleaningUp : Inactive 상태에서 Unconfigured 상태로 전환 중인 상태를 나타내는 용어이다. </li>
<li>ShuttingDown : node가 finalized 상태로 전환 중인 상태를 나타내는 용어이다. </li>
<li>Activating : Inactive 상태에서 Active 상태로 전환 중인 상태를 나타내는 용어이다. </li>
<li>Deactivating : Active 상태에서 Inactive 상태로 전환 중인 상태를 나타내는 용어이다. </li>
<li>ErrorProcessing : Configuring, CleaningUp, Activating, Deactivating, ShuttingDown이 진행 중일 때 의도치않은 에러가 발생되면 Finalized상태로 넘기는 과정을 나타내는 용어이다. 이 상태가 진행되면 node는 finalized상태가 되고 메모리가 해제되면서 종료된다. </li>
</ul>
</li>
</ul>
</li>
<li><p>Lifecycle node</p>
<ul>
<li>lifecycle기능을 갖춰 상태 관리 및 제어가 가능한 node를 의미한다. </li>
</ul>
</li>
</ul>
<hr>
<h1 id="준비물">준비물</h1>
<ul>
<li><a href="https://velog.io/@i_robo_u/ROS2%EC%9D%98-%EA%BD%83-publisher%EC%99%80-subscriberC-%ED%8E%B8">pub/sub 개념 및 구현</a></li>
<li><a href="https://velog.io/@i_robo_u/%EB%82%B4-%EB%A1%9C%EB%B4%87%EC%9D%84-%ED%95%9C%EC%B8%B5-%EB%8D%94-%EB%98%91%EB%98%91%ED%95%98%EA%B2%8C-ROS2-Service-Server%EC%99%80-Client-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0">service server/client 개념 및 구현</a></li>
</ul>
<hr>
<h1 id="실습">실습</h1>
<h2 id="실습1-패키지-만들기">실습1. 패키지 만들기</h2>
<p>이번엔 simple_lifecycle_pkg라는 이름으로 패키지를 만들어보자. </p>
<pre><code>mkdir -p simple_lifecycle_ws/src
cd simple_lifecycle_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 simple_lifecycle_pkg
cd simple_lifecycle_pkg
code . </code></pre><p>패키지 내부 경로로 이동해서 visual code까지 열어줬다. </p>
<hr>
<h2 id="실습2-talker-lifecycle-node작성하기">실습2. talker lifecycle node작성하기</h2>
<p>자 그 다음 우리 talker lifecycle node를 작성해야한다. 
이 node는 우리가 일전에 <a href="https://velog.io/@i_robo_u/ROS2%EC%9D%98-%EA%BD%83-publisher%EC%99%80-subscriberC-%ED%8E%B8">pub/sub 구현 실습</a>
에서 작성한 것과 매우 유사한 기능을 가지고 있다.(살짝 출력하는 log는 지도 작성 기능 예시에 맞게 변경했다.) </p>
<p>먼저 소스코드를 작성할 파일, lifecycle_talker.cpp을 다음과 같이 생성해주자. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/d8138b2e-3bd9-4d78-b34b-ef4fa007d0a1/image.png" alt=""></p>
<p>그다음 내부에 다음 코드를 먼저 복사 붙여넣기 하자. 항상 말하지만 나중에 스스로 구현해야되면 이 코드를 reference삼아 구성요소 및 흐름을 이해하고 있다고 써먹으면 된다.   </p>
<pre><code>#include &lt;chrono&gt;
#include &lt;functional&gt;
#include &lt;memory&gt;
#include &lt;string&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;

#include &quot;rclcpp_lifecycle/lifecycle_node.hpp&quot;

using namespace std::chrono_literals;

// class MinimalPublisher : public rclcpp::Node
class MinimalPublisher : public rclcpp_lifecycle::LifecycleNode
{
public:
    MinimalPublisher()
        // : Node(&quot;minimal_publisher&quot;), count_(0)
        : rclcpp_lifecycle::LifecycleNode(&quot;minimal_publisher&quot;), count_(0)
    {
        // publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(&quot;topic&quot;, 10);
        // timer_ = this-&gt;create_wall_timer(
        //     500ms, std::bind(&amp;MinimalPublisher::timer_callback, this));
    }

    rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_configure(const rclcpp_lifecycle::State &amp;state)
    {
        publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(&quot;topic&quot;, 10);
        timer_ = this-&gt;create_wall_timer(1s, std::bind(&amp;MinimalPublisher::timer_callback, this));

        RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on_configure() is called from state %s.&quot;, state.label().c_str());

        return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
    }

    rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_activate(const rclcpp_lifecycle::State &amp;state)
    {

        publisher_-&gt;on_activate();

        RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on_activate() is called from state %s.&quot;, state.label().c_str());

        return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
    }

    rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &amp;state)
    {
        publisher_-&gt;on_deactivate();

        RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on_deactivate() is called from state %s.&quot;, state.label().c_str());

        return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
    }

    rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &amp;state)
    {

        timer_.reset();
        publisher_.reset();

        RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on cleanup is called from state %s.&quot;, state.label().c_str());

        return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
    }

    rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &amp;state)
    {

        timer_.reset();
        publisher_.reset();

        RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on shutdown is called from state %s.&quot;, state.label().c_str());

        return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
    }

private:
    void timer_callback()
    {
        auto message = std_msgs::msg::String();
        message.data = &quot;Creating a map &quot; + std::to_string(count_++);
        // Print the current state for demo purposes
        if (!publisher_-&gt;is_activated())
        {
            RCLCPP_INFO(
                get_logger(), &quot;Lifecycle publisher is currently inactive. Messages are not published.&quot;);
        }
        else
        {
            RCLCPP_INFO(get_logger(), &quot;Lifecycle publisher is active. Publishing: [%s]&quot;, message.data.c_str());
        }

        // We independently from the current state call publish on the lifecycle
        // publisher.
        // Only if the publisher is in an active state, the message transfer is
        // enabled and the message actually published.
        publisher_-&gt;publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    std::shared_ptr&lt;rclcpp_lifecycle::LifecyclePublisher&lt;std_msgs::msg::String&gt;&gt; publisher_;
    size_t count_;
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    std::shared_ptr&lt;MinimalPublisher&gt; minimal_publisher = std::make_shared&lt;MinimalPublisher&gt;();
    rclcpp::spin(minimal_publisher-&gt;get_node_base_interface());
    // rclcpp::spin(std::make_shared&lt;MinimalPublisher&gt;()); //origianl
    rclcpp::shutdown();
    return 0;
}</code></pre><h3 id="실습-21-코드-분석">실습 2.1. 코드 분석</h3>
<p>자 이제 코드 분석을 해보자. </p>
<h4 id="실습-211-헤더파일-포함-및-namespace선언">실습 2.1.1 헤더파일 포함 및 namespace선언</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/9878bd70-73e1-42f7-9f11-d7c45c6b0a8c/image.png" alt=""></p>
<p>먼저 필요한 헤더파일들은 표준 라이브러리와 ROS2에서 include하고 있다. </p>
<p>나머지는 전부다 일반 talker node를 만들 때 배웠던 거라 익숙하지만 </p>
<pre><code>#include &quot;rclcpp_lifecycle/lifecycle_node.hpp&quot;</code></pre><p>는 처음본다. 
해당 헤더파일은 일반 node가 아닌 lifecycle node라는 것을 만들 때 필요한 헤더파일이다. </p>
<p>추후 어디서 어떻게 사용하기 위해 include된 것 인지 나머지 코드를 보면서 직접 살펴보자. </p>
<h4 id="실습-212-lifecycle-class-생성자-생성">실습 2.1.2. lifecycle class 생성자 생성</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/80930f9c-4fc2-49a9-b7c2-6304f23d85ee/image.png" alt=""></p>
<p>위의 코드를 보면 기존의 Node를 상속 받던 부분, publisher와 timer를 생성하는 부분이 class 생성자(constructor) 부분에서 빠진 것을 볼 수 있다. </p>
<pre><code>...
// : Node(&quot;minimal_publisher&quot;), count_(0)
...
        // publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(&quot;topic&quot;, 10);
        // timer_ = this-&gt;create_wall_timer(
        //     500ms, std::bind(&amp;MinimalPublisher::timer_callback, this));  </code></pre><p>대신 rclcpp_lifecycle::LifecycleNode를 상속받고 있는데 lifecycle node는 일반 node와 다르게 해당 node의 상태를 확인할 수 있고 제어할 수 있는 기능을 갖는다. </p>
<p>Publisher와 timer 생성은 해당 class의 method인 on_configure에서 생성될 예정이다. </p>
<h4 id="실습-213-on_configure-작성하기">실습 2.1.3. on_configure() 작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/70619c31-b2b9-4fec-bcc2-bdd980032ba9/image.png" alt=""></p>
<p>앞서 용어 설명에서 on_configure는 unconfigure 상태에서 inactive 상태로 변환 시켜주는 기능을 한다고 배웠다. 
즉, publisher/subscriber, timer, service 등을 생성하는 역할을 담당한다. </p>
<p>그렇기 때문에 MinimalPublisher class의 on_configure() method는 publisher와 timer를 만들고 있다. 
만드는 방식은 일반 node와 다르지 않지만 여기서 &quot;publisher_&quot;는 그 타입이 일반 publisher가 아닌데 밑에서 해당 타입을 정의하는 부분에서 살펴볼 에정이다. </p>
<p>그리고 on_configure()가 성공적으로 동작했으면 성공했다는 </p>
<pre><code>rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS</code></pre><p>를 반환한다. </p>
<h4 id="실습-214-on_activate-작성하기">실습 2.1.4. on_activate() 작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/6e893b1c-cc09-4178-ab70-e05492db1ba7/image.png" alt=""></p>
<p>on_activate()의 경우 inactive에서 active로 lifecycle node의 상태를 변환한다. active상태에서는 실제로 publisher/subscriber 등이 데이터를 생성 및 처리하고 service 호출에 응답하는 등의 실질 적인 기능이 동작해야한다. </p>
<p>이 lifecycle node의 경우 publihser_를 on_active()라는 method를 통해서 실질적으로 publisher_가 메세지를 발행할 수 있는 상태가 될 수 있도록 활성화 시키고 있다. 
이 on_active()가 어떤 영향을 끼치는지는 timer callback을 살펴볼 때 살펴볼 예정이다. </p>
<p>그리고 on_activate()가 성공적으로 동작했으면 성공했다는 </p>
<pre><code>rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS</code></pre><p>를 반환한다. </p>
<h4 id="실습-215-on_deactivate-작성하기">실습 2.1.5. on_deactivate() 작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/4059c8aa-cfbe-497c-8818-52a303d25499/image.png" alt=""></p>
<p>on_deactivate()의 경우 active에서 inactive로 node의 상태를 변화 시킨다. </p>
<p>inactive상태에서는 publisher/subscriber, service등이 동작하지 않아야 하기 때문에 publisher_를 on_deactive()를 통해서 비활성화한다. </p>
<p>이렇게 비활성화 되면 timer callback에서 publisher_를 통해 message 발생 명령을 내려도 실제로 그 기능을 하지 못 한다. </p>
<p>그리고 on_deactivate()가 성공적으로 동작했으면 성공했다는 </p>
<pre><code>rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS</code></pre><p>를 반환한다. </p>
<h4 id="실습-216-on_cleanup-작성하기">실습 2.1.6 on_cleanup() 작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/0a72081e-876d-4f4c-a241-99f65d96d41f/image.png" alt=""></p>
<p>on_cleanup()은 re-configure를 하기 전에 생성되었던 publisher/subscriber, service, 필요없는 변수들을 청소하는 작업을 한다. 또한 node 상태를 inactive에서 unconfigured로 변경한다. </p>
<p>따라서 코드에서는 이전에 생성했던 timer_와 publisher_의 메모리를 해제하고 있다. </p>
<p>그리고 on_cleanup()가 성공적으로 동작했으면 성공했다는 </p>
<pre><code>rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS</code></pre><p>를 반환한다. </p>
<h4 id="실습-217-on_shutdown-작성하기">실습 2.1.7 on_shutdown() 작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/f5268181-3164-4b3c-be29-6239b623378e/image.png" alt=""></p>
<p>on_shutdown()은 unconfigured, inactive, active 상태에서 호출 될 수 있으므로 node가 기능을 잃기전 안전하게 필요없는 메모리를 해제해줘야하는 역할을 수행해야한다. </p>
<p>그렇기 때문에 다소 on_cleanup()과 유사해보일 수 있다. </p>
<p>해당 코드에서는 일전에 생성되어있을 수 있었던 timer_, publisher_의 메모리를 해제한다. </p>
<p>그리고 on_shutdown()가 성공적으로 동작했으면 성공했다는 </p>
<pre><code>rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS</code></pre><p>를 반환한다. </p>
<h4 id="실습-218-timer_callback작성하기">실습 2.1.8. timer_callback작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/62e99261-ecfd-416e-8188-4a958b867873/image.png" alt=""></p>
<p>publsher_의 publisher를 생성할 때 등록해줘야하는 timer_callback이 정상적으로 동작하기 위해서 private 접근 지정자에서 정의해줘야한다. </p>
<p>해당 timer_callback 코드에서는</p>
<ul>
<li>발행할 메세지를 만들어 놓는다. 우리는 지도 그리는 예시를 사용하므로 &quot;Creating a map&quot;이라는 메세지를 작성했다.  </li>
<li>publisher_가 active상 태인지 확인하고 그에 맞는 log를 출력한다. </li>
<li>일전에 만들어 놓은 메세지를 publisher_로 발행하는데 이 코드는 publisher_가 on_activate()에 의해 active 상태에 있을 때만 동작한다.  </li>
</ul>
<h4 id="실습-219-클래스내-멤버-변수-선언">실습 2.1.9. 클래스내 멤버 변수 선언</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/ecd15a26-38ff-47df-91d0-6e386a60aa43/image.png" alt=""></p>
<p>메세지를 발행할 때 필요한 publisher, timer, count 변수들을 선언한다. </p>
<p>한가지 특이한건 publisher_의 타입이 </p>
<pre><code>std::shared_ptr&lt;rclcpp_lifecycle::LifecyclePublisher&lt;std_msgs::msg::String&gt;&gt;</code></pre><p>라는 것이다.</p>
<p>이렇게 on_active, on_deactive으로 활성화/비활성화 할 수 있는 publisher를 만들기 위해서는 rclcpp_lifecycle 헤더로부터 LifecyclePublisher라는 타입을 사용해야한다. </p>
<h4 id="실습-2110-main-함수-작성하기">실습 2.1.10. main 함수 작성하기</h4>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/0b8d6958-72a1-44a0-aaa8-0ab5f9251898/image.png" alt=""></p>
<p>이제 node가 executable을 실행했을 때 동작할 수 있도록 main함수를 작성해야 한다. </p>
<p>여기서 독특한 점은 rclcpp spin을 사용할 때 이전처럼 class 자체를 넣어주는게 아니라 node의 base interface를 획득하여 넣어준다. </p>
<p>이렇게 하는 이유는 lifecycle node 특성상 노드의 상태를 관찰 및 제어하는데 그 제어권등을 get_node_base_interface로 획득해서 spin으로 넘겨줘야 하기 때문이다. </p>
<h3 id="실습-22-cmakeliststxt-작성">실습 2.2. CMakeLists.txt 작성</h3>
<p>소스코드 작성이 끝났으면 다음 함수들을 CMakeLists.txt에 추가해주자. </p>
<pre><code>find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)

add_executable(lifecycle_talker src/lifecycle_talker.cpp)
ament_target_dependencies(lifecycle_talker rclcpp std_msgs rclcpp_lifecycle)

install(TARGETS
  lifecycle_talker
  DESTINATION lib/${PROJECT_NAME})
</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/8fbee42a-77ef-424c-8a52-f03e21226c57/image.png" alt=""></p>
<p>여기서 주목할 점은 우리는 lifecycle node와 lifecycle publisher를 사용했기 때문에 해당 타입을 갖고 있는 &quot;rclcpp_lifecycle&quot; 패키지를 찾고 executable을 생성할 때 dependency로 넣어줬다는 것이다. </p>
<p>dependency 이야기를 하니 생각나는게 있는가?</p>
<p>그렇다 package.xml도 수정해주자!</p>
<h3 id="실습-23-packagexml-작성">실습 2.3. package.xml 작성</h3>
<p>다음 코드를 추가해주자. </p>
<pre><code>  &lt;depend&gt;rclcpp&lt;/depend&gt;
  &lt;depend&gt;std_msgs&lt;/depend&gt;

  &lt;build_depend&gt;rclcpp_lifecycle&lt;/build_depend&gt;</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/22edfa60-6339-4ce3-93c8-e025d1c66b3e/image.png" alt=""></p>
<p>rclcpp와 std_msgs는 일전에 봤던 것과 같다. 
그런데 dependency로 rclcpp_lifecycle을 추가해줬다. </p>
<hr>
<h2 id="실습3-빌드-및-실행">실습3. 빌드 및 실행</h2>
<p>대망의 빌드 시간이다. </p>
<p>다음과 같이 colcon build해주자. </p>
<pre><code>cd ~/simple_lifecycle_ws
colcon build</code></pre><p>빌드가 완료되면 실행하자. </p>
<pre><code>cd ~/simple_lifecycle_ws
source install/setup.bash
ros2 run simple_lifecycle_pkg lifecycle_talker</code></pre><p>오잉?</p>
<p>메세지가 나오지 않는다.</p>
<p>뭐가 잘 못 된 것일까?</p>
<hr>
<h2 id="실습4-rqt로-껐다-켰다-하는-service-call-보여주기">실습4. rqt로 껐다 켰다 하는 service call 보여주기</h2>
<p>일반 node와 lifecycle node가 다른 점은 node를 껐다 켰다 할 수 있다는 점이다. </p>
<p>그렇기 때문에 앞서 실습3에서 executable을 실행해줬지만 node 상태가 unconfigured라서 아무 메세지도 출력되지 않는 것이다. </p>
<p>lifecycle node를 만들 때 on_configure, on_active, on_deactive, on_cleanup, on_shutdown method를 구현해주면 이에 상응하는 service들을 이용할 수 있게 된다. </p>
<p>오늘은 두가지 서비스에 대해서 배워볼 예정이다. </p>
<h3 id="실습41-rqt-service-caller-켜기">실습4.1. rqt service caller 켜기</h3>
<p>rqt service caller가 익숙하지 않은 사람은 <a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">이 글</a> 에서 5.rqt 실습을 참고 바란다. </p>
<p>다음과 같이 두개의 service caller를 열어준다. 두개의 service caller를 구성하려면 service caller를 구성하는 것을 단순히 2번 반복하면 된다. </p>
<ul>
<li>하나는 &quot;/minimal_publisher/change_state&quot; 서비스에 대한 caller이다. </li>
<li>나머지 하나는 &quot;/minimal_publisher/get_state&quot; 서비스에 대한 caller이다. </li>
</ul>
<p>(2개의 rqt service caller를 구성한 예시) </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/10c76c1e-af27-467a-9389-dfe6d6ab3253/image.png" alt=""></p>
<h3 id="실습-42-minimal_publisherchange_state-서비스-이용하기">실습 4.2. /minimal_publisher/change_state 서비스 이용하기</h3>
<ul>
<li>/minimal_publisher/change_state 서비스는 minimal_publisher lifecycle node상태를 변환하는데 사용한다. 
변환하기 위한 int type Expression은 다음과 같다. </li>
<li>1: on_configure()을 호출</li>
<li>2: on_cleanup()을 호출</li>
<li>3: on_activate()를 호출</li>
<li>4: on_deactivate()를 호출</li>
<li>5: unconfigured상태에서 on_shutdown()을 호출 </li>
<li>6: inactive상태에서 on_shutdown()을 호출</li>
<li>7: active 상태에서 on_shutdown()을 호출</li>
</ul>
<p>여기서는 다음과 같은 시나리오를 실습해보자</p>
<ul>
<li>on_configure() 호출하기
(예시) 
<img src="https://velog.velcdn.com/images/i_robo_u/post/6ce32f0c-be0d-4f09-b906-17e36ec4a8b8/image.png" alt=""></li>
</ul>
<ul>
<li><p>그 후, on_activate() 호출하기 
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/88d6fd94-6bd8-48be-aeb7-6e59b45b4716/image.png" alt=""></p>
</li>
<li><p>그 후, on_deactivate() 호출하기 
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/fca1f8cf-6d6a-43bf-9c0b-b533bed37b81/image.png" alt=""></p>
</li>
<li><p>그 후, on_cleanup()호출하기
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/510b85f7-aedf-43f0-8e26-3716be1d91c7/image.png" alt=""></p>
</li>
<li><p>마지막으로 unconfigured 상태일 때 on_shutdown() 호출하기 
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/49256281-5b32-4dba-97c5-dafb3b4a608b/image.png" alt=""></p>
</li>
</ul>
<h3 id="실습-43-minimal_publisherget_state-서비스-이용하기">실습 4.3. /minimal_publisher/get_state 서비스 이용하기</h3>
<p>해당 서비스는 이용하는 방법이 간단하다. </p>
<p>그냥 우측 상단에 call을 누르면 lifecycle node의 현재 상태를 알려준다. </p>
<p>위 실습 4.2를 진행하면서 다음과 같은 상태들을 확인해보자. </p>
<ul>
<li>on_configure() 호출하기 전 상태 : &quot;unconfigured&quot;
(예시) 
<img src="https://velog.velcdn.com/images/i_robo_u/post/cfe8fd08-a2c2-4c78-8582-1271d99475eb/image.png" alt=""></li>
</ul>
<ul>
<li>on_configure() 호출한 후 상태 : &quot;inactive&quot;
(예시) 
<img src="https://velog.velcdn.com/images/i_robo_u/post/391b5da3-2f6f-4d20-aa57-10e3d1cdb9fa/image.png" alt=""></li>
</ul>
<ul>
<li>그 후, on_activate() 호출 한 후 상태: &quot;active&quot;
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/b7dfb741-051f-43b7-b1ea-05b877472864/image.png" alt=""></li>
</ul>
<ul>
<li>그 후, on_deactivate() 호출한 후 상태 : &quot;inactive&quot; 
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/a22499c2-ee40-4164-8b69-b4d6d93b8264/image.png" alt=""></li>
</ul>
<ul>
<li>그 후, on_cleanup()호출한 후 상태: &quot;unconfigured&quot;
(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/9bff8fda-e0fd-45a3-973a-d842fade10c2/image.png" alt=""></li>
</ul>
<ul>
<li>마지막으로 unconfigured 상태일 때 on_shutdown() 호출한 후 상태 : &quot;finalized&quot;<br>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/e0789ead-91be-4501-9767-afd285f02467/image.png" alt=""></li>
</ul>
<hr>
<h2 id="실습5-lifecycle-listener-code-구현하기">실습5. lifecycle listener code 구현하기</h2>
<ul>
<li>lifecycle talker가 발행하는 메세지를 구독하는 lifecycle node</li>
<li>아쉽게도 rclcpp_lifecycle::LifecyclePublisher와 같이 LifecycleSubscription이 없어서 on_activate(), on_deactivate() 같은 함수를 사용할 수 없다. 그렇지만 lifecycle의 state machine 기본 구조를 충분히 이해하고 약간의 c++프로그래밍적 요소를 더하면 충분히 lifecycle node를 만들기 위해 필요한 on_configure, on_activate, on_deactivate, on_cleanup, on_shutdown 함수들을 구현할 수 있다. </li>
<li>자 그렇다면 본격적으로 lifecycle talker가 발행하는 메세지를 구독하는 lifecycle node를 만들어보자. </li>
<li>먼저 src폴더에 다음과 같이 lifecycle_listener.cpp라는 파일을 만들어준다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/aee9ddde-7044-4b98-b403-58d58158124e/image.png" alt=""></li>
<li>그 다음 이 파일에 다음의 코드를 작성하도록 하자.<pre><code>#include &lt;memory&gt;
</code></pre></li>
</ul>
<p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;
using std::placeholders::_1;</p>
<p>#include &quot;rclcpp_lifecycle/lifecycle_node.hpp&quot;</p>
<p>class MinimalSubscriber : public rclcpp_lifecycle::LifecycleNode
{
public:
    MinimalSubscriber()
        : rclcpp_lifecycle::LifecycleNode(&quot;minimal_subscriber&quot;)
    {
        // subscription_ = this-&gt;create_subscription&lt;std_msgs::msg::String&gt;(
        //     &quot;topic&quot;, 10, std::bind(&amp;MinimalSubscriber::topic_callback, this, _1));
    }</p>
<pre><code>rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_configure(const rclcpp_lifecycle::State &amp;state)
{
    subscription_ = this-&gt;create_subscription&lt;std_msgs::msg::String&gt;(
        &quot;topic&quot;, 10, std::bind(&amp;MinimalSubscriber::topic_callback, this, _1));
    m_isActivated = false;
    RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on_configure() is called from state %s.&quot;, state.label().c_str());

    return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}

rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_activate(const rclcpp_lifecycle::State &amp;state)
{
    m_isActivated = true;
    RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on_activate() is called from state %s.&quot;, state.label().c_str());

    return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}

rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_deactivate(const rclcpp_lifecycle::State &amp;state)
{
    m_isActivated = false;
    RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on_deactivate() is called from state %s.&quot;, state.label().c_str());

    return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}

rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_cleanup(const rclcpp_lifecycle::State &amp;state)
{
    subscription_.reset();
    RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on cleanup is called from state %s.&quot;, state.label().c_str());

    return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}

rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_shutdown(const rclcpp_lifecycle::State &amp;state)
{
    subscription_.reset();
    RCUTILS_LOG_INFO_NAMED(get_name(), &quot;on shutdown is called from state %s.&quot;, state.label().c_str());

    return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
}</code></pre><p>private:
    void topic_callback(const std_msgs::msg::String &amp;msg) const
    {
        if (m_isActivated)
        {
            RCLCPP_INFO(this-&gt;get_logger(), &quot;I heard: &#39;%s&#39;&quot;, msg.data.c_str());
        }
        else
        {
            RCLCPP_INFO(
                get_logger(), &quot;Subscriber is currently inactive. Callback function is not working .&quot;);
        }
    }
    rclcpp::Subscription&lt;std_msgs::msg::String&gt;::SharedPtr subscription_;
    bool m_isActivated = false;
    // std::shared_ptr&lt;rclcpp_lifecycle::LifecycleSusbcription&lt;std_msgs::msg::String&gt;&gt; subscription_; -&gt; unfortunately no such thing
};</p>
<p>int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    std::shared_ptr<MinimalSubscriber> minimal_subscriber = std::make_shared<MinimalSubscriber>();
    rclcpp::spin(minimal_subscriber-&gt;get_node_base_interface());
    // rclcpp::spin(std::make_shared<MinimalSubscriber>());
    rclcpp::shutdown();
    return 0;
}</p>
<pre><code>### 실습 5.1. 코드 분석 

그럼 lifecycle 용 subscription이 없는데 어떻게 lifecycle node 처럼 동작시킬 수 있는지 위의 코드를 하나하나 분석해보자! 

#### 실습 5.1.1. 필수 헤더파일 추가 
제일 먼저 살펴볼 부분은 필수 헤더파일을 추가하는 것이다. 

![](https://velog.velcdn.com/images/i_robo_u/post/1f2b3ca7-293a-4936-b4a5-a8af0c263d72/image.png)
- ROS2 관련 코드를 위한 rclcpp.hpp
- string type의 message를 사용하기 위한 string.hpp
- lifecycle node를 상속받기 위한 lifecycle_node.hpp 
- std::shared_ptr를 사용하기 위한 memory 
    - (참고: 표준 라이브러리인 memory는 헤더파일이지만 .hpp나 .h라는 확장자를 갖지 않는다고 한다.)
- 등의 헤더파일을 include하는 것을 볼 수 있다. 

#### 실습 5.1.2. lifecycle node 상속 및 constructor 정의 

다음 과정은 node를 만들려면 class가 필요하기 때문에 class 생성자(constructor)를 정의해준다! 

![](https://velog.velcdn.com/images/i_robo_u/post/4b6c8cdc-c813-4b01-ab0c-e00d572a92cb/image.png)
- MinimalSubsriber 클래스는 LifecycleNode를 상속받는다. 
- lifecycle node를 상속 받았기 때문에 on_configure, on_activate, on_deactivate, on_cleanup, on_shutdown 함수를 override할 수 있다. 
- MinimalSubscriber() 생성자에서 lifecycle node 이름은 &quot;minal_subscriber&quot;로 초기화하고 있다. 
- 예전에 배웠던 기본 listener에서와는 다르게 생성자에서 subscription을 생성하고 callback을 묶어주는 작업을 하지 않는다. 
- lifecycle 정의상 subscription이 생성되는 것은 on_configure가 호출될 때 행해지는 것이 적절하기 때문이다. 

#### 실습 5.1.3. on_configure 정의 

lifecycle node의 구색을 갖추기 위해서는 on_configure(), on_activate(), on_deactivate(), on_cleanup(), on_shutdown()이라는 함수가 필요했었다. 
on_configure()부터 정의하자. 

![](https://velog.velcdn.com/images/i_robo_u/post/2876ff39-9d6f-437c-a076-d17d051060d1/image.png)

- 외부 사용자(client등)가 해당 lifecycle node를 configure할 수 있도록 도와주는 함수 on_configure를 정의한다. 
- configure에서는 node가 주요기능을 하기 위해 필요한 변수, 객체등을 생성하는 구간이라고 생각하면 된다.
- 여기서는 subscription이 필요하므로 &quot;topic&quot; 이름을 갖는 topic message를 구독하는 subscription을 생성하고 이 subscription을 topic_callback이라는 함수를 등록해준다. 
- topic_callback은 &quot;topic&quot;에서 message를 받을 때마다 실행된다. i.e. message가 들어오지 않으면 topic_callback 함수 호출 안됨.
- m_isACtivated라는 멤버변수를 false로 지정한다. 
- 이는 아직 해당 lifecycle node가 active한 상태는 아니라는 것을 의미한다. 
- on_configure가 호출 될 때 어떤 상태였는지 state.label()을 통해 log를 출력한다. 
- on_configure내 코드들이 정상적으로 동작했다면 마지막에 함수 호출이 성공했다는 의미에서 CallbackReturn::SUCCESS라는 값을 반환한다.

#### 실습 5.1.4. on_activate 정의

좋아 on_configure()를 정의했다. 이제 on_activate로 이동하자! 

![](https://velog.velcdn.com/images/i_robo_u/post/2f518207-2f5c-41cb-8884-39c7ff74e22f/image.png)

- m_isActivated라는 멤버 변수가 true로 설정된다. 
- 이 멤버 변수 값이 true이면 topic_callback 호출 시 특정 동작을 취하게 된다. 
- 이는 lifecycle node 정의인 activated상태일 때 주요 기능을 node가 수행한다는 것에 부합한다. 
- 그 후 어떤 상태에서 on_activate가 호출 되었는지 log를 출력한다. 
- on_activate 함수 호출이 성공했다는 의미에서 CallbackReturn::SUCESS를 반환한다. 

#### 실습 5.1.5. on_deactivate 정의

on_activate도 별거 아니였다. on_deactivate는 어떨까? 한 번 살펴보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/21f044db-fc35-4805-b55c-decb6763b00d/image.png)
- lifecycle node의 deativated 상태의 정의에 의하면 해당 node에서 주요 기능인 subscription기능이 실행되지 않으면 된다. 
- 그렇게 하기 위해 m_isActivated를 false로 설정한다. 
- 이렇게 설정하는게 subscription 동작을 멈추게 하는 것과 무슨 연관이 있을까? 
- 밑에 topic_callback에서 살펴보겠지만 m_isActivated 값이 false인 경우 topic_callback에서는 아무 동작을 하지 않고 함수를 종료한다. 
- 그렇기 때문에 m_isActivated를 false로 해놓으면 deactivated 상태를 만들 수 있다!

#### 실습 5.1.6. on_cleanup 정의 

lifecycle node 정의를 알고 있으니 on_deactivate도 너무 쉬웠다. 
on_cleanup에서는 무엇을 해야할까? 
그렇다. on_configure에서 생성해줬던 기능 수행시 필요한 변수, 객체등을 제거해줘야한다. 
여기서는 subscription이 그 변수, 객체등에 해당한다. 
이를 제거하기 위해 다음과 같이 코드를 작성할 수 있다. 

![](https://velog.velcdn.com/images/i_robo_u/post/c1335cb8-f349-4d61-ac52-b8eba96cd857/image.png)
- cpp에서는 shared pointer 변수를 제거하기 위해 reset()을 사용할 수 있다. reset()으로 subscription을 제거해준다. 
- state.label()로 어떤 상태에서 on_cleanup()이 호출 되었는지 log를 출력한다. 
- on_cleanup()이 성공적으로 수행되었다면 CallbackReturn::SUCCESS를 반환한다. 

#### 실습 5.1.7. on_shutdown 정의 

거의 다왔다. 이제 on_shutdown()만 정의하면된다. shutdown은 무슨 역할을 했던가? 그렇다. node가 없어지기전 node에 있던 변수들의 메모리등을 안전하게 해제해주면된다. 
그렇기 때문에 cleanup에서 하는 작업과 다소 겹치는 부분이 있다. 
어떻게 하는지 코드로 살펴보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/1e4f6c00-9145-402a-a85a-b11ef5814c32/image.png)

- subscription pointer를 reset()해준다. 
- 이 node는 워낙 간단해서 pointer의 메모리만 해제해주면 shutdown의 역할이 끝났다고 볼 수 있다. 
- 그렇기 때문에 on_cleanup()과 코드가 거의 유사하다. 
- 추가로 어떤 state에서 on_shutdown()이 호출 되었는지 출력한다. 
- 성공적으로 on_shutdown()호출이 마무리 되면 CallbackReturn::SUCCESS를 반환한다. 

#### 실습 5.1.8. topic_callback 정의 

좋아! 이제 lifecycle node 기본 기능(안전하게 끄고, 켜기)을 위한 함수들을 모두 구현했다. 

이제 실제 node가 수행해야하는 기능(listenr: 메세지를 듣고 적절한 행동을 취하는 것)을 위한 디테일들을 신경써보자. 

먼저 topic message를 구독할 때 수행하고 싶은 동작을 정의해야한다. 

![](https://velog.velcdn.com/images/i_robo_u/post/6223b3cf-614f-40da-961b-4a1d6b6fd7f0/image.png)


- topic_callback함수는 String type의 msg를 인자로 받는다. 
- on_configure, on_activate, on_deactivate등에 의해 결정되는 m_isActivated 변수가 true인 경우 listener의 역할인 msg의 data를 출력하는 코드를 실행한다. 
- 그렇지 않은 경우 node가 inactive상태(deacitvated)이므로 inactive상태라는 log를 출력한다. 

#### 실습 5.1.9. 멤버 변수 정의 
이제 우리가 사용했던 멤버 변수들을 초기화해주는 과정을 살펴보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/efe2affb-7e1c-44b4-ba15-5f15ff7df247/image.png)

- subscription_을 type에 맞게 선언한다. 이 때 이 때 변수 이름뒤에 붙어있는 underbar는 해당 변수가 멤버변수임을 가리킨다. 
- m_isActivated 변수를 false로 초기화한다. 이 변수에 따라 topic_callback의 동작 여부가 결정된다. 

#### 실습 5.1.10. main함수에서 node 생성하기
드이어 대망의 node 생성하는 부분이다. 

실제로 node가 생성되고 동작하는 코드이므로 매우 중요한 부분이라고 볼 수 있다. 

![](https://velog.velcdn.com/images/i_robo_u/post/3c23f0c2-0438-4d4f-99a7-49c5f1b7c650/image.png)

- rclcpp::init으로 ROS2 node등을 사용할 것을 알린다. init을 해줘서 node의 run time등을 초기화해준다고 간단히 생각할 수 있다. 
- shared_ptr을 이용해서 MinimalSubscriber 객체를 생성한다. 
- 객체가 생성되면서 앞서 구현했던 lifecycle node가 생성된다. 
- 생성한 node의 base interface를 get_node_base_interface()로 획득하여 rclcpp::spin에 넘겨준다. 
- 기본적인 node interface를(node를 통제할 수 있는 수단 같은 것) 넘겨준다고 이해하면 쉽다. 
- rclcpp::spin은 callback들이 호출 될 수 있게 해주므로 꼭 코드에 포함되어야한다. 
- **가끔 node 객체를 생성하고 spin을 빼먹는 경우가 있는데 이러면 ROS2는 아무 동작을 하지않으므로 주의하자!** 
- 프로그램을 종료시키는 명령어등(ctrl+C) 등이 들어오면 rclcpp::shutdown()을 통해 해당 ROS2 코드를 종료한다. 
- 성공적으로 프로그램이 끝났음을 알리는 0을 반환하고 main 함수를 종료한다. 

하나하나 설명하느라 길었지만 결국에는 lifecycle node에 subscription이 있을 때도 on_configure, on_activate, on_deactive, on_cleanup, on_shutdown이 lifecyce node의 현재 상태를 어떤 상태로 만들어야하는지만 기억하면 구현하는 것 자체는 간단하다! 

### 실습 5.2. CMakeLists.txt와 package.xml 작성 
source code를 작성했으니 이제 CMakeLists.txt와 package.xml을 수정해주자. 

다음 코드를 적절히 추가해준다. 

full code는 [github repository](https://github.com/IROBOU/simple_lifecycle_pkg)에 업로드해놨으니 참고하자!

- executable 추가하기 위한 함수들을 작성한다. </code></pre><p>add_executable(lifecycle_listener src/lifecycle_listener.cpp)
ament_target_dependencies(lifecycle_listener rclcpp std_msgs rclcpp_lifecycle)</p>
<pre><code>![](https://velog.velcdn.com/images/i_robo_u/post/ea4753d3-2894-4436-9c1c-c19a06dc399b/image.png)

- executable을 install하기 위한 함수를 작성한다. </code></pre><p>install(TARGETS
  lifecycle_talker
  lifecycle_listener
  #lifecycle_service_client
  DESTINATION lib/${PROJECT_NAME})</p>
<pre><code>
- 참고 lifecycle_Service_client라는 executable을 아직 작성하지 않았으므로 실습시 제거하도록하자.(실습 6에서 작성할 예정) 
![](https://velog.velcdn.com/images/i_robo_u/post/6b00b9ae-301d-4db8-ba01-19f29feac19b/image.png)

-package.xml 같은 경우는 lifecycle_listener가 lifecycle_talker와 의존 패키지들(rclcpp, std_msgs, rclcpp_lifecycle)이 동일하므로 따로 추가할 것이 없다. 

#### 실습 5.2.1. build 및 실행하기 
이제 build하면 된다. 
다음 명령어들을 각각 실행해주자. 
</code></pre><p>cd ~/simple_lifecycle_ws
colcon build</p>
<pre><code>
빌드가 완료되면 실행하자. </code></pre><p>cd ~/simple_lifecycle_ws
source install/setup.bash
ros2 run simple_lifecycle_pkg lifecycle_listener</p>
<pre><code>
아마 아무 메세지도 출력되지 않을 것이다. 

당연하다. rqt를 열어서 lifecycle_talker 실습에서 처럼 service call을 해보자. 

이는 실습 5.3의 개인 연습으로 남겨두도록 하겠다. 

**힌트:** change_state 서비스를 호출 할 때 현재 lifecycle node상태와 호출하려는 상태의 순서를 주의하자. 
잘 모르겠을 시 lifecycle node state machine 그림을 참고하면서 천천히 해보자. 


### 실습 5.3 rqt 통한 lifecycle node 제어 service 호출 (개인연습)

![](https://velog.velcdn.com/images/i_robo_u/post/c7a1840e-580a-42bd-8888-e89a31292554/image.png)




---

## 실습6. 껐다 켰다 하는 service 코드 만들기 
- rqt의 service caller를 통해 lifecycle node를 껐다 켰다 했는데, 이는 service client code를 통해서도 같은 동작이 가능하다는 의미이다. 

- service client code에 대해 개념이 가물가물 한 사람들은 예전 내용을 가볍게 읽어보도록 하자. 
- [Service Server 와 Client 코드 작성하기 링크](https://velog.io/@i_robo_u/%EB%82%B4-%EB%A1%9C%EB%B4%87%EC%9D%84-%ED%95%9C%EC%B8%B5-%EB%8D%94-%EB%98%91%EB%98%91%ED%95%98%EA%B2%8C-ROS2-Service-Server%EC%99%80-Client-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0)

### 실습 6.1. 코드 작성
- 먼저 src 폴더에 &quot;lifecycle_service_client.cpp&quot; 라는 파일을 만들자. 
![](https://velog.velcdn.com/images/i_robo_u/post/14263b9b-e5e8-4027-8b37-76be70129cb2/image.png)

- 일단 다음의 코드를 복사 붙여넣자. 해당 코드는 [여기](https://github.com/ros2/demos/tree/humble/lifecycle) 에서 발췌 및 수정했음을 참고하자. </code></pre><p>// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     <a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.</p>
<p>#include <chrono>
#include <memory>
#include <string>
#include <thread></p>
<p>#include &quot;lifecycle_msgs/msg/state.hpp&quot;
#include &quot;lifecycle_msgs/msg/transition.hpp&quot;
#include &quot;lifecycle_msgs/srv/change_state.hpp&quot;
#include &quot;lifecycle_msgs/srv/get_state.hpp&quot;</p>
<p>#include &quot;rclcpp/rclcpp.hpp&quot;</p>
<p>#include &quot;rcutils/logging_macros.h&quot;</p>
<p>using namespace std::chrono_literals;</p>
<p>// which node to handle
static constexpr char const *lifecycle_node = &quot;lc_talker&quot;;</p>
<p>// Every lifecycle node has various services
// attached to it. By convention, we use the format of
// <node name>/<service name>.
// In this demo, we use get_state and change_state
// and thus the two service topics are:
// lc_talker/get_state
// lc_talker/change_state
static constexpr char const *node_get_state_topic = &quot;minimal_publisher/get_state&quot;;
static constexpr char const *node_change_state_topic = &quot;minimal_publisher/change_state&quot;;</p>
<p>template &lt;typename FutureT, typename WaitTimeT&gt;
std::future_status
wait_for_result(
    FutureT &amp;future,
    WaitTimeT time_to_wait)
{
  auto end = std::chrono::steady_clock::now() + time_to_wait;
  std::chrono::milliseconds wait_period(100);
  std::future_status status = std::future_status::timeout;
  do
  {
    auto now = std::chrono::steady_clock::now();
    auto time_left = end - now;
    if (time_left &lt;= std::chrono::seconds(0))
    {
      break;
    }
    status = future.wait_for((time_left &lt; wait_period) ? time_left : wait_period);
  } while (rclcpp::ok() &amp;&amp; status != std::future_status::ready);
  return status;
}</p>
<p>class LifecycleServiceClient : public rclcpp::Node
{
public:
  explicit LifecycleServiceClient(const std::string &amp;node_name)
      : Node(node_name)
  {
  }</p>
<p>  void
  init()
  {
    // Every lifecycle node spawns automatically a couple
    // of services which allow an external interaction with
    // these nodes.
    // The two main important ones are GetState and ChangeState.
    client_get_state_ = this-&gt;create_client&lt;lifecycle_msgs::srv::GetState&gt;(
        node_get_state_topic);
    client_change_state_ = this-&gt;create_client&lt;lifecycle_msgs::srv::ChangeState&gt;(
        node_change_state_topic);
  }</p>
<p>  /// Requests the current state of the node
  /**</p>
<ul>
<li><p>In this function, we send a service request</p>
</li>
<li><p>asking for the current state of the node</p>
</li>
<li><p>lc_talker.</p>
</li>
<li><p>If it does return within the given time_out,</p>
</li>
<li><p>we return the current state of the node, if</p>
</li>
<li><p>not, we return an unknown state.</p>
</li>
<li><p>\param time_out Duration in seconds specifying</p>
</li>
<li><p>how long we wait for a response before returning</p>
</li>
<li><p>unknown state</p>
</li>
<li><p>/
unsigned int
get_state(std::chrono::seconds time_out = 3s)
{
auto request = std::make_shared&lt;lifecycle_msgs::srv::GetState::Request&gt;();</p>
<p>if (!client_get_state_-&gt;wait_for_service(time_out))
{
  RCLCPP_ERROR(</p>
<pre><code>  get_logger(),
  &quot;Service %s is not available.&quot;,
  client_get_state_-&gt;get_service_name());</code></pre><p>  return lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN;
}</p>
<p>// We send the service request for asking the current
// state of the lc_talker node.
auto future_result = client_get_state_-&gt;async_send_request(request).future.share();</p>
<p>// Let&#39;s wait until we have the answer from the node.
// If the request times out, we return an unknown state.
auto future_status = wait_for_result(future_result, time_out);</p>
<p>if (future_status != std::future_status::ready)
{
  RCLCPP_ERROR(</p>
<pre><code>  get_logger(), &quot;Server time out while getting current state for node %s&quot;, lifecycle_node);</code></pre><p>  return lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN;
}</p>
<p>// We have an succesful answer. So let&#39;s print the current state.
if (future_result.get())
{
  RCLCPP_INFO(</p>
<pre><code>  get_logger(), &quot;Node %s has current state %s.&quot;,
  lifecycle_node, future_result.get()-&gt;current_state.label.c_str());</code></pre><p>  return future_result.get()-&gt;current_state.id;
}
else
{
  RCLCPP_ERROR(</p>
<pre><code>  get_logger(), &quot;Failed to get current state for node %s&quot;, lifecycle_node);</code></pre><p>  return lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN;
}
}</p>
<p>/// Invokes a transition
/**</p>
</li>
<li><p>We send a Service request and indicate</p>
</li>
<li><p>that we want to invoke transition with</p>
</li>
<li><p>the id &quot;transition&quot;.</p>
</li>
<li><p>By default, these transitions are</p>
</li>
<li><ul>
<li>configure</li>
</ul>
</li>
<li><ul>
<li>activate</li>
</ul>
</li>
<li><ul>
<li>cleanup</li>
</ul>
</li>
<li><ul>
<li>shutdown</li>
</ul>
</li>
<li><p>\param transition id specifying which</p>
</li>
<li><p>transition to invoke</p>
</li>
<li><p>\param time_out Duration in seconds specifying</p>
</li>
<li><p>how long we wait for a response before returning</p>
</li>
<li><p>unknown state</p>
</li>
<li><p>/
bool
change_state(std::uint8_t transition, std::chrono::seconds time_out = 3s)
{
auto request = std::make_shared&lt;lifecycle_msgs::srv::ChangeState::Request&gt;();
request-&gt;transition.id = transition;</p>
<p>if (!client_change_state_-&gt;wait_for_service(time_out))
{
  RCLCPP_ERROR(</p>
<pre><code>  get_logger(),
  &quot;Service %s is not available.&quot;,
  client_change_state_-&gt;get_service_name());</code></pre><p>  return false;
}</p>
<p>// We send the request with the transition we want to invoke.
auto future_result = client_change_state_-&gt;async_send_request(request).future.share();</p>
<p>// Let&#39;s wait until we have the answer from the node.
// If the request times out, we return an unknown state.
auto future_status = wait_for_result(future_result, time_out);</p>
<p>if (future_status != std::future_status::ready)
{
  RCLCPP_ERROR(</p>
<pre><code>  get_logger(), &quot;Server time out while getting current state for node %s&quot;, lifecycle_node);</code></pre><p>  return false;
}</p>
<p>// We have an answer, let&#39;s print our success.
if (future_result.get()-&gt;success)
{
  RCLCPP_INFO(</p>
<pre><code>  get_logger(), &quot;Transition %d successfully triggered.&quot;, static_cast&lt;int&gt;(transition));</code></pre><p>  return true;
}
else
{
  RCLCPP_WARN(</p>
<pre><code>  get_logger(), &quot;Failed to trigger transition %u&quot;, static_cast&lt;unsigned int&gt;(transition));</code></pre><p>  return false;
}
}</p>
</li>
</ul>
<p>private:
  std::shared_ptr&lt;rclcpp::Client&lt;lifecycle_msgs::srv::GetState&gt;&gt; client_get_state_;
  std::shared_ptr&lt;rclcpp::Client&lt;lifecycle_msgs::srv::ChangeState&gt;&gt; client_change_state_;
};</p>
<p>/**</p>
<ul>
<li><p>This is a little independent</p>
</li>
<li><p>script which triggers the</p>
</li>
<li><p>default lifecycle of a node.</p>
</li>
<li><p>It starts with configure, activate,</p>
</li>
<li><p>deactivate, activate, deactivate,</p>
</li>
<li><p>cleanup and finally shutdown</p>
</li>
<li><p>/
void callee_script(std::shared_ptr<LifecycleServiceClient> lc_client)
{
rclcpp::WallRate time_between_state_changes(0.1); // 10s</p>
<p>// configure
{
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_CONFIGURE))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}</p>
<p>// activate
{
  time_between_state_changes.sleep();
  if (!rclcpp::ok())
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}</p>
<p>// deactivate
{
  time_between_state_changes.sleep();
  if (!rclcpp::ok())
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_DEACTIVATE))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}</p>
<p>// activate it again
{
  time_between_state_changes.sleep();
  if (!rclcpp::ok())
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}</p>
<p>// and deactivate it again
{
  time_between_state_changes.sleep();
  if (!rclcpp::ok())
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_DEACTIVATE))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}</p>
<p>// we cleanup
{
  time_between_state_changes.sleep();
  if (!rclcpp::ok())
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_CLEANUP))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}</p>
<p>// and finally shutdown
// Note: We have to be precise here on which shutdown transition id to call
// We are currently in the unconfigured state and thus have to call
// TRANSITION_UNCONFIGURED_SHUTDOWN
{
  time_between_state_changes.sleep();
  if (!rclcpp::ok())
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;change_state(lifecycle_msgs::msg::Transition::TRANSITION_UNCONFIGURED_SHUTDOWN))
  {</p>
<pre><code>return;</code></pre><p>  }
  if (!lc_client-&gt;get_state())
  {</p>
<pre><code>return;</code></pre><p>  }
}
}</p>
</li>
</ul>
<p>void wake_executor(std::shared_future<void> future, rclcpp::executors::SingleThreadedExecutor &amp;exec)
{
  future.wait();
  // Wake the executor when the script is done
  // <a href="https://github.com/ros2/rclcpp/issues/1916">https://github.com/ros2/rclcpp/issues/1916</a>
  exec.cancel();
}</p>
<p>int main(int argc, char **argv)
{
  // force flush of the stdout buffer.
  // this ensures a correct sync of all prints
  // even when executed simultaneously within the launch file.
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);</p>
<p>  rclcpp::init(argc, argv);</p>
<p>  auto lc_client = std::make_shared<LifecycleServiceClient>(&quot;lc_client&quot;);
  lc_client-&gt;init();</p>
<p>  rclcpp::executors::SingleThreadedExecutor exe;
  exe.add_node(lc_client);</p>
<p>  std::shared_future<void> script = std::async(
      std::launch::async,
      std::bind(callee_script, lc_client));
  auto wake_exec = std::async(
      std::launch::async,
      std::bind(wake_executor, script, std::ref(exe)));</p>
<p>  exe.spin_until_future_complete(script);</p>
<p>  rclcpp::shutdown();</p>
<p>  return 0;
}</p>
<pre><code>
뭐가 굉장히 길다.. 

포기할까? 당연히 아니다. 

너무 겁먹을게 없는게 반복되는 작업이 많아서 그렇지 원리는 간단한다
- configure 호출한다.
- activate 호출한다.
- deactivate 호출한다.
- cleanup 호출한다. 
- shutdown 호출한다. 

이걸 code로 작성하기 위해 부가적인 코드들이 많이 추가됐을 뿐이다. 

자 이제 그 무서워보이는 코드. 파헤쳐보자!! 

### 6.2. 코드 분석

#### 6.2.1. 필요 헤더파일 추가하기 
역시 코드 작성 제일 처음에는 헤더파일들을 추가해줘야한다. 
그래야 원하는 함수등을 쓸 수 있을테니 말이다. 
![](https://velog.velcdn.com/images/i_robo_u/post/dc3849d7-6071-4b90-beea-63e2c78905eb/image.png)

- chrono, memory, string은 많이 소개했으니 넘어가자. 
- thread 헤더
  - 이 헤더는 std::async와 같은 함수를 제공한다. thread란 정말 간단하게 생각해서 일하는 사람이라고 생각할 수 있다. 
따라서 std::async같은 함수를 사용하게되면 사람A가 업무1을 하고 있는데 동시에 사람B가 업무2를 사람A가 일을 하고 있던 말던 할 수 있게 해준다. 
- state.hpp
  - lifecycle_msgs라는 package에서 온 이 헤더파일은 lifecycle node 관련 state를 나타내는 (e.g. lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN) 데이터를 사용하기 위해 include한다. 
-  transition.hpp
   - 이 헤더는 lifecycle node의 state간 변화(변이) 과정을 나타내는  lifecycle_msgs::msg::Transition::TRANSITION_CLEANUP 과 같은 데이터를 사용하기 위해 include한다. 
- change_state.hpp
   - 이 헤더파일을 lifecycle node의 상태를 변경할 때 필요한 service message를 사용하기 위해 추가한다. 
- get_state.hpp
   - 이 헤더파일을 lifecycle node의 상태를 확인할 때 필요한 service message를 사용하기 위해 추가한다. 
- rclcpp.hpp 많이 나왔으므로 생략
- logging_macros.h 
   - 이 헤더파일을 RCUTILS_LOG_INFO와 같은 log 출력 매크로를 사용하기 위해 추가한다. 
- using namespace std::chrono_literals
   - 이 namespace는 많이 설명했으므로 생략 

#### 6.2.2. lifecycle node 및 service 이름(service topic) 변수 설정

![](https://velog.velcdn.com/images/i_robo_u/post/299dd088-8f3f-4c5c-af73-5f46d7fced84/image.png)

- 위와 같이 lifecycle node 및 service 이름을 나타내는 변수를 설정한다. 
- 여기서 lifecycle node 이름인 &quot;lc_talker&quot;는 우리가 앞서 개발했던 lifecycle_talker를 가리킨다. 
- 여기서 &quot;node_get_state_topic&quot;와 &quot;node_change_state_topic&quot;는 우리가 service client로 호출할 service topic 이름을 설정하는 변수이다. 
- lifecycle_talker executable에서 제공되는 service의 이름은 각각 &quot;minimal_publisher/get_state&quot;와 &quot;minimal_publisher/change_state&quot;이다. 
- 그 밖에도 몇가지 낯선 키워드들이 눈에 보인다. 간단히 살펴보자
- **static**: class를 객체로 만들지 않아도 외부에서 접근할 수 있는 변수를 만든다. 
- **constexpr**: 컴파일 시간에 변수가 아닌 상수 취급을 하여 처리할 수 있다. 프로그램 속도등을 최적화 할 때 사용할 수 있는 프로그래밍 키워드이다. 낯설다면 안써도 된다. 
- **const**: 해당 변수는 한번 정해지면 변경할 수 없다는 뜻이다. node이름, service topic 이름등을 변경할 수 있는 시스템을 만들지 않겠다는 뜻에서 이 변수들을 const 처리한다. 
- **char \***: string의 시작점의 주소를 나타낼 때 사용하는 키워드이다. \*를 사용하면 pointer(주소)를 사용하게 된다고 생각하면 기억하기 수월하다. 



#### 6.2.3. client node를 위한 class 만들기 
service를 호출할 client는 node형태로 만들어진다. 
이미 알다시피 node는 class 형태로 만들 수 있다. 


![](https://velog.velcdn.com/images/i_robo_u/post/4fcbe730-157a-4c7c-8d68-36e2e209f13d/image.png)

- Node를 상속 받아서 LifecycleServiceClient라는 클래스를 선언한다. 
- class의 생성자는 node_name을 인자로 받는다. 
- 이 node_name으로 Node의 이름을 초기화 시킨다. 
- 해당 class를 생성할 때 인자인 node_name을 명시적으로 사용하게 하기 위해서 explicit이라는 키워드를 사용했다. 
- 이 class의 생성자에서는 node_name을 초기화하는 것 이외에는 딱히 다른 기능을 하지않는다. 

#### 6.2.4. client 생성하는 함수 구현하기 
이 node의 주요 기능은 lifecycle_talker의 service를 request하는 것이다. 따라서 client가 필요하므로 client들을 만들어주자. 

먼저 client 변수들을 private으로 만들어준다. 
![](https://velog.velcdn.com/images/i_robo_u/post/f2371070-966f-4846-913d-b4a586009506/image.png)

- client들은 rclcpp::Client의 shared pointer로 만들어지고, lifecycle_msgs의 service message인 GetState, ChangeState를 각각 사용한다.  

이 client들은 이제 init()함수가 호출될 때 다음과 같이 생성된다. 

![](https://velog.velcdn.com/images/i_robo_u/post/28901518-69ea-4382-8b41-22e091f4bd1f/image.png)
- this(node class를 가리킴)-&gt;create_client를 이용해서 앞서 정의했던 node_get_state_topic과 node_change_State_topic 이름에 해당하는 service를 request하는 client를 생성한다. 
- 이 client는 service message를 구성하여 request할 때 두고두고 사용될 예정이다. 

#### 6.2.5. get_state 함수 구현(part1)

그럼 이제 이 client 들을 사용하는 함수를 구현해보자. 

lifecycle node의 상태를 바꾸고 나서 유효한 상태인지 체크하는 것이 디버깅할 때 유리하다. 

그러면 node 상태를 획득할 수 있는 get_state함수를 살펴보자.  

client_get_state를 사용해서 lifecycle node의 현재 상태를 획득할 것이다. 

먼저 아래 코드를 살펴보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/2bd82cd0-9c3a-4bbb-af59-c904170c023a/image.png)

- 먼저 state를 get하기 위해 필요한 request 변수를 만들어준다. 
- 그 다음 앞서 만들어 놨던 client인 client_get_state_의 wait_for_service 함수를 이용해서 이 client가 이용하고자 하는 service가 현재 유효할 때까지 기다린다. 
- 그런데 가게가 열지 않을 때 계속 기다리면 의미가 없듯이, 여기서도 time_out 만큼만 기다린다. 
- time_out값은 get_state함수의 인자로 들어오는데 기본값은 3초로 설정했다. 
- 3초동안 client가 이용하고자하는 service인 &quot;minimal_publisher/get_state&quot;가 없으면 현재 사용불가능하다는 메세지를 출력하고, 현재 상태가 불분명하다는 의미인 PRIMARY_STATE_UNKNOWN이라는 값을 반환한다. 
- 만약 service가 이용가능하면 앞서 만들었던 request를 async_send_request 함수를 사용해서 service server에 보내고 그 결과를 future_result로 받는다. 
- 이렇게 획득한 future_result는 해당 작업이 끝났는지를 체크할 때 사용된다. 
- 즉, 어떤 이유에 의해서 service request가 즉각적으로 처리되서 lifecycle node의 state를 바로 획득할 수도 있고 그렇지 못 할 수 도있다. 
- 참고: future라는 이름이 붙은 이유는 코드가 불린 시점(request 한 이후)에 결과가 나오기 때문에 미래라는 의미에서 future를 붙인 것이다. 
- 그래서 이 future_result값을 time_out과 함께 wait_for_result라는 함수에 전달한다. 
- 이 함수는 service request 작업이 time_out 이전에 끝났는지를 판단하는 결과를 반환한다. (자세한 설명은 하단에서 wait_for_reuslt함수 설명을 참고) 

#### 6.2.6 get_state 함수 구현(part2)

이어서 함수의 나머지 부분을 살펴보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/8c6c7948-da10-473b-b8f1-e00f5ae092ba/image.png)


- 먼저 wait_for_result로 future_result 가 time_out이내에 들어왔는지 확인한다. 
- time_out 이내에 result가 준비가 됐다면 future_status 값은 std::future_status::ready가 되어야 한다. 
- 그렇지 않은 경우 현재 service server에서 request처리가 무슨 이유에서인지 시간초과 됐다고 알리고 현재 lifecycle node 상태는 불분명 하다는 값을 반환한다. 
- future_status가 준비된 경우 future_result.get()을 통해서 future_result가 유효한 값을 갖고 있는지 확인한다. 
- future_result.get()이 nullptr이면 제대로 상태를 받지 못한 것인데 그 이유로는 service server가 어떤 이유에서인지 유효한 값을 반환하지 못했거나, 응답이 없거나 할 수 있다. 
- 그럴 경우 에러 메세지와 함께 현 상태 알수 없다는 값을 반환한다. 
- future_result.get()이 nullptr가 아닌 유효한 경우, current_state.label(active, inactive, configured등) 을 log로 출력하고 label에 대응되는 current_state.id를 반환한다. 

이렇게 lifecycle의 node 상태를 획득하는 client 코드를 구현했다. 
복잡한거 같지만 service server가 응답하지 않을 수 있는 상태를 여러 if문에 거쳐 확인하고 node 상태를 유효하게 획득한 경우에만 처리하는 것을 길게 써놓았을 뿐이다. 

목적은 간단하다. node의 상태 획득하는거!


#### 6.2.7 wait_for_result 함수 구현
그렇다면 get_state 함수에서 사용된 wait_for_result는 어떻게 구현될까? 

실제 숫자를 넣어 생각하면 수월하니 예시를 들어 살펴보자. 

![](https://velog.velcdn.com/images/i_robo_u/post/3f0c9c62-406a-4477-8910-444a4c9e7d1b/image.png)

- time_to_wait 가 10초라고 가정하자
- steady_clock::now()에서 얻은 현재 시간이 50초라고 하면 end 값은 60초가된다. 
- 즉 현재 시간 50초로부터 60초까지만 기다릴 것이다. 
- 기다리는 주기(wait_period)는 100 milisecond(0.1초) 이다. 
- 기본적으로 status는 시간초과라고 설정한다. 
- do-while 구문에 진입하면 현재 시간 now를 획득한다. now를 51초라고 가정하자 
- end 60초에서 now 51초를 뺀 9초를 time_left라고 하자. 
- time_left가 0 이하가 되면 do-while문을 break하고 현재 status를 반환한다. 
- 그렇지만 아직 time_left가 9초이므로 break되지 않는다. 
- time_left와 wait_period 중 작은 시간을 선택해 future는 그 시간만큼 기다린다. 
- 여기선 wait_period 0.1초가 더 적으므로 0.1초동안 기다려서 status를 획득한다. 
- 아직 아직 status가 ready상태가 아니면 do-while 구문을 반복한다. 
- now는 계속 시간이 흐름에 따라 값이 증가하므로 언젠가 이 do-while 구문은 status가 ready가되지 않아도 break된다. 
- status 가 ready 된 순간에도 do-while 구문은 동작을 멈춘다. 

이러한 절차를 거쳐 service로 요청한 작업이 준비가 됐는지 안됐는지를 판단한다. 

아직 많이 낯설다면 &quot;아 이렇게 service 요청을 기다리는 코드를 짤 수 있구나&quot; 하고 넘어가고 나중에 직접 짜야할 때 해당 코드를 참고하면된다. 걱정은 No! 


#### 6.2.8 change_state 함수 구현 
앞서 node 상태 확인하는 함수를 구현했는데, 이제는 어떤 함수를 구현해야할까?

그렇다! 본격적으로 node의 상태를 바꾸는 함수가 필요하다. 

이 코드에서는 change_state함수를 통해 해당 기능을 수행하고 있다. 

어떻게 바꾸는지 하나하나 살펴보자. 

![](https://velog.velcdn.com/images/i_robo_u/post/0be8a7b6-5dfd-4b76-81f6-2e681b76c3e0/image.png)

- transition과 time_out을 인자로 받는다. 
- transition은 바꾸고자 하는 node의 상태를 위한 행동이다. 예를 들어 node를 active로 바꾸기위해서는 activate를해야 하므로 이때 transition은 &quot;lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE&quot; 이 된다. 
- time_out은 어떤 행동의 시간초과를 판단할 때 사용하는 인자이다. 고정값은 3초이다. 
- 먼저 ChangeState::Request type의 service request를 생성한다. 
- request의 transition.id 값을 transition으로 설정한다. 이 값은 node를 어떤 상태로 바꿀 것인지를 결정한다. 
- client_change_state_의 wait_for_service 함수를 통해서 time_out에 설정되어 있는 시간동안 기다리면서(e.g. 3초) 이 client가 이용할 service가 준비되어 있는지 확인한다. 
- 준비되어 있지 않다면 false를 반환하고 함수를 종료한다. false는 node 상태 변경을 실패했다는 의미이다. 
- service가 준비됐으면 앞서 만든 상태 변화 request를 async_send_request를 통해 보내고 future_result를 획득한다. 
- 참고로 여기서 share()를 사용하면 이 future_result를 다른 thread에서도 사용할 수 있다. 참고만 하자. 
- 그 후 get_state에서와 마찬가지로 wait_for_result함수에 인자로 넘겨주어 future_status가 ready 인지 timeout인지 판단한다. 
- future_status가 ready가 아니면 service server에서 request를 처리하는 동안 시간초과를 해버린 것이므로 변경 실패라고 판단하고 함수를 종료한다. 
- 반면에 timeout 시간내에 future_state가 ready됐으면 future_result.get()-&gt;sucess 또한 nullptr이 아닌 유효한 값인지 확인하여 성공했으면 성공한 상태를, 실패했으면 실패했다는 메세지를 각각 출력한다. 
- node 상태 변화를 성공했으면 true, 아니면 false를 반환한다. 

복잡해보이지만 change_state 절차를 정리하자면 다음과 같다. 괄호 안에 있는 것은 실생활에 비유한 예시이니 참고하도록하자.
1. request 메세지를 만든다. (e.g. 먹고 싶은 음식 생각) 
2. service가 준비되었는지 확인한다.(e.g. 식당 열렸는지 확인)
3. request를 보낸다. (e.g. 키오스크로 음식주문)
4. request가 시간내 처리됐는지 확인 (e.g. 음식이 너무 늦게 나오지 않는지 확인)
5. request가 제대로 처리됐는지 확인 (e.g. 내가 시킨 음식이 알맞게 나왔는지 확인)

코드는 긴거 같지만 논리는 별거아닌 것 같다. ㅎㅎ

LifecycleServiceClient class내에서 구현해야할 것은 다했다. 

이제 뭐가 더 필요할까?

#### 6.2.9. callee_script 함수 구현하기

LifecycleServiceClient class내 멤버 method를 상태를 바꿀때마다 호출하면 되지만 이 과정을 좀 더 자동화할 순 없을까?

이를 callee_script라는 함수를 추가로 구현해서 처리할 수 있다. 

lifecycle node 상태를 제어하는 시나리오는 무수히 많겠지만 이 함수에서는 다음과 같은 시나리오로 node 상태를 변화시킬 것이다. 

- configure -&gt; activate -&gt;deactivate -&gt; activate -&gt; deactivate -&gt; cleanup -&gt; shutdown

자 그럼 어떻게 코드를 구성할 수 있는지 파헤쳐보자! 

![](https://velog.velcdn.com/images/i_robo_u/post/91fefe37-2ae2-4701-89df-9f2806f710fe/image.png)

- LifecycleServiceClient의 shared_ptr을 인자로 받는다. 
- 이 lc_client는 node 상태를 변경하고 획득하는데 사용될 것이다. 
- WallRate를 이용해서 time_between_state_changes라는 것을 선언한다. 상태변화 시도간 얼마나 쉴지를 의미하며 0.1은 0.1 Hz 즉 1초당 0.1번 즉 10초의 간격을 준다는 의미이다. 
- 앞서 구현해놓은 change_state 함수를 TRANSITION_CONFIGURE 인자로 호출한다. 
- congifure 동작을 수행하기 위한 인자이다. 
- 상태 변경을 정상적으로 성공했을 때 다음 if 구문으로 넘어간다. 
- get_state method를 호출하여 유효한 상태로 변경됐는지 확인한다. 
- 유효한 상태일 때만 다음 구문으로 넘어간다. 
- 10초동안 동작이 완료될 때 까지 대기한다. 
- configure를 해서 inactive상태이다. 이 node를 activate하자. lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE 를 change_state method의 인자로 넘겨주면 된다. 
- 역시 잘 변경됐는지 상태를 체크한다. 


![](https://velog.velcdn.com/images/i_robo_u/post/587b90ad-94f8-4060-867f-4c8e4ccfafee/image.png)
- 10초동안 동작이 완료될 때 까지 대기한다.
- deactivete 과정을 진행한다. transition을 위한 인자는 lifecycle_msgs::msg::Transition::TRANSITION_DEACTIVATE를 사용한다.
- 다시 activate한다. 


![](https://velog.velcdn.com/images/i_robo_u/post/401fb5c4-4d6b-4f8e-85d1-97358c687c1f/image.png)
- 다시 deactivate한다. 
- cleanup 과정도 유사하다. transition을 위한 인자는 lifecycle_msgs::msg::Transition::TRANSITION_CLEANUP를 사용한다. 

![](https://velog.velcdn.com/images/i_robo_u/post/315a7ec0-5450-4d76-9519-c9df06e981cb/image.png)
- 마지막으로 unconfigured 상태일 때 shutdown을 진행한다. 이렇게 shutdown의 종류를 구분 짓는 이유는 shutdown은 unconfigured, inactive, active 모든 상태에서 호출 될 수 있기 때문이다. 

#### 6.2.10. main 함수 작성
자 그럼 이제 본격적으로 callee_script를 호출할 main 함수를 작성해보자. 

아직 안배운 executor가 등장하지만 겁먹지 말자!
![](https://velog.velcdn.com/images/i_robo_u/post/3a298140-2518-448e-963e-32414109640a/image.png)

- servbuf로 출력한 메세지를 위한 버퍼를 세팅하는 구문을 작성한다. 이 글에서 학습하고자하는 주제와 크게 연관되지 않으니 넘어가도록 하자. 
- rclcpp::init으로 ROS를 사용할 준비하자. 
- LifecycleServiceClient class의 객체를 share_ptr 로 만든 lc_client를 생성한다.
- 이 객체의 init method로 client들을 생성한다. 
- rclcpp::executors::SingleThreadedExecutor 타입의 exe를 선언한다.
- 나중에 배우겠지만 executor는 node의 기능을 수행하는 일꾼들이라 생각하면 된다. 이 일꾼들이 어떻게 일하는가에 따라 그 타입이 결정된다. 
- 일꾼들에게 lc_client라는 class(node)를 add_node라는 함수를 통해서 추가해준다. 일꾼들이 무슨 일을 해야하는지 알려주기 위함이다. 
- 여기서부터 조금 헷갈릴 수 있는데 그냥 읽어보고 &quot;아 lifecycle node 바꾸는 작업을 이렇게 요청했구나~&quot; 정도로 생각하고 넘어가도 좋다. 
- 먼저 std::bind를 사용해서 callee_script가 호출될 때 방금 생성한 lc_client를 인자로 받도록 한다. 
- 그리고 std::aync에서 std::launch::async 정책을 이용하여 callee_script를 호출하는데 이 정책은 &quot;지금 새로운 thread(일 할수 있는 사람)으로 callee_script 처리해줘&quot; 라는 의미이다. 
- 그리고 그 결과는 shared_future 타입으로 script에 반환되게 된다. 
- 그럼 이 shared_future 타입 script를 wake_executor라는 함수의 인자로 exe와 같이 std::bind로 묶어서 전달해주게 된다. 이 과정 역시 새로운 thread를 만들어서 실행하는 정책을 사용한다. 
- 그 결과는 wake_exec에 저장한다. 
- wake_executor 함수에서 어떤 일이 벌어지는 지는 밑에서 설명하겠다. 
- 그럼 callee_script는 지금 실행되고 있는 중인데 시간이 꽤 걸린다. 왜냐면 앞서 봤듯이 우리는 node 상태를 10초에 한번씩 변경하고 있기 때문이다. 
- 그렇기 때문에 exe.spin_until_future_complete(script) 함수를 통해서 shared_future script가 완료되길 기다린다. 
- callee_script가 완료 되면 wake_executor내에서 또 다른 작업이 이뤄진다. 
- 바로 살펴보자! 

![](https://velog.velcdn.com/images/i_robo_u/post/1c963286-f771-4853-9bb3-7bde87439a51/image.png)

- 코드는 간단하다. 
- 먼저 인자로 들어온 future(여기선 script에 해당)의 wait()를 호출한다. 
- 이렇게되면 이 future를 위한 작업이 다 진행될 때까지(여기선 callee_script) 기다리는 효과를 얻을 수 있다. 
- 그 후 call_script가 다 끝났다면 wait()가 막고 있던 코드 다음으로 넘어가 exec.cancel()이 호출된다. 
- 이 exec.cancel()은 우리가 만들었던 executor에서 하고 있는 동작들을 정지시키고 메모리를 정리한다. 
- 우리 프로그램에서 이제 executor를 사용하지 않을 것이니까 치우는 과정이라고 보면된다. 
- 그러면 main 함수에서 rclcpp::shutdown()이 호출되고 프로그램이 0을 반환하고 종료된다. 


자. 뭔가 호출하고 기다리는 과정이 많았다. 
특히 async라는 표현과 future, wait 등 낯선 용어들이 있었는데 간단히 요약하면
- async는 다른일도 하면서 새로운일도 동시에 하고 싶을 때 (비동기적) 사용되는 경우가 많다. 
- future는 async로 수행되는 업무는 시간이 좀 걸릴 수 있으므로 업무 수행 시간에 대한 미래 결과를 관리할 수 있게 도와준다. 
- wait는 async로 수행되는 업무가 끝날 때까지 기다리는 역할을 한다. 

전체적으로 다시 main 함수를 정리하자면
- node 상태 변경 client를 만든다.
- client로 상태 변경하는 코드를 async로 호출한다. 
- 결과를 기다린다. 
- 안전하게 일한 사람(thread)를 정리한다. 
- 프로그램을 종료한다. 

자 이렇게 대망의 lifecycle_service_client.cpp 분석이 끝났다. 기지개 쭉 펴고 빌드하기 위해 CMakeLists.txt와 package.xml을 수정하러 가자!

### 6.3 CMakeLists.txt 와 package.xml 수정 및 빌드

- CMakeLists.txt 수정

CMakeLists.txt에 다음과 같이 executable 이름과 dependencies를 추가한다. 해당 executable을 lifecycle_msgs에 의존성을 갖는다는 것을 상기하자. 전체 완성본은 github코드를 참고하기!
</code></pre><p>add_executable(lifecycle_service_client src/lifecycle_service_client.cpp)
ament_target_dependencies(lifecycle_service_client
  &quot;lifecycle_msgs&quot;
  &quot;rclcpp_lifecycle&quot;
)</p>
<pre><code>
추가로 lifecycle_service_client를 설치하는 코드를 다음과 같이 추가한다. 
</code></pre><p>install(TARGETS
  lifecycle_talker
  lifecycle_listener
  lifecycle_service_client
  DESTINATION lib/${PROJECT_NAME})</p>
<pre><code>
- package.xml 수정
lifecycle_msgs에 의존하므로 다음을 추가한다. </code></pre><p><depend>lifecycle_msgs</depend></p>
<pre><code>
변경사항들을 저장하고 새로운 터미널을 열어 빌드한다. 
</code></pre><p>cd ~/simple_lifecycle_ws
colcon build</p>
<pre><code>
### 6.4 lifecycle_service_client.cpp 실행하기 

자 그럼 lifecycle_service_client를 실행해보자!

이때 lifecycle_talker를 먼저 실행해주는 것을 잊지말자. 

- lifecycle_talker 실행하기 </code></pre><p>cd ~/simple_lifecycle_ws
source install/setup.bash
ros2 run simple_lifecycle_pkg lifecycle_talker</p>
<pre><code>
- lifecycle_service_client 실행하기 
</code></pre><p>cd ~/simple_lifecycle_ws
source install/setup.bash
ros2 run simple_lifecycle_pkg lifecycle_service_client</p>
<pre><code>
그러면 client가 lifecycle_talker의 상태를 변화시키면서 다음과 같은 메세지들이 각각의 터미널에서 출력될 것이다. 

- client 실행한 터미널
![](https://velog.velcdn.com/images/i_robo_u/post/6c1a4928-17d0-42e6-89cf-a1798af20aa5/image.png)

- talker 실행한 터미널
![](https://velog.velcdn.com/images/i_robo_u/post/bf269203-803a-417a-b0d5-d242b7a8c759/image.png)

자세히 살펴보면 inactive -&gt; active -&gt; inactive -&gt; active -&gt; inactive -&gt; unconfigured -&gt; finalized 가 된 것을 확인할 수 있다. 

야호!



---
# 요약
이번시간에는 이렇게 lifecycle node가 왜 필요하며 어떻게 구현할 수 있는지 알아봤다. 

수많은 용어가 등장하긴 했지만 사실 본질은 사용하는 node는 켜주고 사용하지 않는 node는 꺼주고 이상이 있는 node는 어떤 상태에 있는지 등을 파악하기 위한 것으로 간단한다. 

그러니 너무 어렵게 생각하지말자! 

state machine을 보면서 오늘 배운 용어를 다시 머릿속으로 정리해보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/ba104139-6fc3-41c7-82bb-14882e906fbb/image.png)

- lifecycle만드는 순서 요약 
    - 1. lifecycle node를 상속받아 클래스를 정의한다. 
    - 2. on_configure, on_activate, on_deactive, on_cleanup, on_shutdown이 각 상태에서 적절히 동작하도록 구현한다. 
    - 3. main함수에서 node의 base interface를 획득하여 spin에 넘겨준다. 
    - 4. CMakeLists.txt를 lifecycle을 사용할 수 있도록 수정한다. 
    - 5. package.xml에서 lifecycle을 사용할 수 있도록 수정한다. 

    이렇게 정리하고 보니 흐름을 5줄로 요약이 가능하다. 

    별거 없네 하면서 자신감을 갖도록 하자. 

    다음시간엔 우리 로봇이 동시에 여러가지 일을 처리할 수 있도록 도와주는 executor와 callback group에 대해 다뤄보도록 하겠다. 많은 관심 바란다. 

- 다음번 주제 executor &amp; callback groups
관련 원문 링크: 

개념 

https://docs.ros.org/en/humble/Concepts/Intermediate/About-Executors.html#executors


예제 

https://docs.ros.org/en/humble/How-To-Guides/Using-callback-groups.html#using-callback-groups

---
질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!

- 문의메일: irobou0915@gmail.com

- 오픈톡 문의: https://open.kakao.com/o/sXMqcQAf

- [IRoboU 유튜브 채널](https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg)


---
# 참고 문헌 
- [base code link](https://github.com/ros2/demos/tree/humble/lifecycle)
- [lifecycle node 개념](https://design.ros2.org/articles/node_lifecycle.html) 
- [로봇 청소기 영상](https://youtu.be/PM1yO5aB-p0?feature=shared)
- [지도그리는 영상](https://www.youtube.com/watch?v=34n1tF5OtQU)


---
![](https://velog.velcdn.com/images/i_robo_u/post/8bad7d7b-766f-4f56-859d-9f9d13b46490/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[의사소통을 더 빠르게! composable node작성하기(C++)]]></title>
            <link>https://velog.io/@i_robo_u/%EC%9D%98%EC%82%AC%EC%86%8C%ED%86%B5%EC%9D%84-%EB%8D%94-%EB%B9%A0%EB%A5%B4%EA%B2%8C-composable-node%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0C</link>
            <guid>https://velog.io/@i_robo_u/%EC%9D%98%EC%82%AC%EC%86%8C%ED%86%B5%EC%9D%84-%EB%8D%94-%EB%B9%A0%EB%A5%B4%EA%B2%8C-composable-node%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0C</guid>
            <pubDate>Sat, 02 Mar 2024 13:00:11 GMT</pubDate>
            <description><![CDATA[<h1 id="--동영상-튜토리얼-">*<em>- <a href="https://youtu.be/XTAac-qxmF4">동영상 튜토리얼</a> *</em></h1>
<h1 id="배경지식">배경지식</h1>
<p>다음 예시를 생각해보자. 
친구 A, B, C가 있다고하자. A는 C에게 하고 싶은 말이 있다. 그렇다면 A가 C에게 말하는게 빠를까 아니면 A가 B한테 말한 후 B가 C에게 말하는게 빠를까?</p>
<p>당연히 A가 C에게 말하는게 더 빠를 것이다. 단순히 생각해보면 A가 B에게 말하고 B가 다시 C에게 말한다면 전달 내용을 B가 기억(저장)해야하기 때문에 B를 통해 전달하는 것이 더 느리다. </p>
<p>ROS2는 A publisher와 C subscriber node가 서로 다른 processor에서 동작한다면 이처럼 A와 C는 B를 통해서 대화하고 있다고 보면 된다. </p>
<p>그렇다면 A와 C가 서로 직접접으로 대화 할 수 있게 하는 방법은 없을까?</p>
<p>당연히 똑똑한 ROS2 개발자들은 이를 염두해두고 ROS2를 개발했기 때문에 이렇게 빠르게 정보를 전달할 수 있는 방법이 존재한다. </p>
<p>바로 두개의 node를 동일한 processor내에서 처리함으로써 정보 교환시 따로 복사하지 않고 바로 전달할 수 있게 하는 방법이다. </p>
<p>이때 새로운 용어들이 등장하는데 이해하고 나면 어렵지 않으니 차근 차근 배워보자. </p>
<h2 id="용어-설명">&lt;용어 설명&gt;</h2>
<ul>
<li><p>component: 제일 처음 배울 용어는 component이다. 
component는 우리가 일전에 배운 node를 가리키는 말이다. 일반적인 node와 다르게 다른 node와 같은 processor내에 묶이는 구성원으로 사용될 수 있으므로 구성 요소(component)라는 이름이 붙였다. </p>
</li>
<li><p>composable node: 뭔가 복잡해보이지만 component와 같은 의미를 갖는다. component node를 여러개 사용하여 밑에 배울 composition(구성)을 만들 것인데 이때 composition에 사용가능한 node라고 해서 composable(구성가능한)이라는 이름이 붙었다. </p>
</li>
<li><p>composition: 한개의 processor내에 composable node(=component)들로 이루어진 형태를 composition(구성)이라고한다. 한개의 processor내(한 composition)에서 node들간 통신을 하는 경우 데이터 복사과정이 없기 때문에 좀 더 빠르고 효율적인 통신이 가능하다. </p>
</li>
</ul>
<p>요약해보자면,
composition은 composable한 node(=component)로 구성된다. 
이 composition을 만들게 되면 node들간 빠른 통신이 가능하다. </p>
<h2 id="왜-필요하지">왜 필요하지?</h2>
<p>&quot;아니, component(=composable node), composition같은 어려운 이름을 써서 왜 더 헷갈리게 할까?&quot; 라는 생각이 드는가?</p>
<p>그럴 수 있다. 그런데 로봇개발은 상대적으로 우리가사용하는 PC보다 느린 컴퓨터에서 개발되므로 효율성이 굉장히 중요하다. 
또한 로봇에서는 이미지 같은 방대한 데이터가 빠른 주기로 처리되야한다. 그렇기 때문에 이런 이미지를 불필요하게 복사하는 행위는 효율성을 떨어뜨릴 수 있다. 
따라서 composition같은 개념을 도입해서 로봇이 좀 더 효율적으로 동작할 수 있게 해야한다. </p>
<hr>
<h1 id="준비물">준비물</h1>
<ul>
<li>pub/sub node작성하는 실습 (<a href="https://velog.io/@i_robo_u/ROS2%EC%9D%98-%EA%BD%83-publisher%EC%99%80-subscriberC-%ED%8E%B8">링크</a>) </li>
</ul>
<hr>
<h1 id="실습">실습</h1>
<h2 id="실습-1-패키지-만들기">실습 1. 패키지 만들기</h2>
<p>항상 해왔듯이 composition 구현 실습을 위한 작업공간과 패키지를 하나 만들자. </p>
<pre><code>mkdir -p composition_ws/src
cd composition_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 simple_composition
cd simple_compositon 
code .</code></pre><p>아직 우리는 component(=composable node)를 만들기 위해 어떤 dependency가 있는지 잘 모르겠으므로 패키지 만들때 따로 dependency를 추가해주지 않았다. </p>
<p>패키지내부 경로로 이동해서 visual code를 열어주자. </p>
<hr>
<h2 id="실습2-componentcomposable-node-작성하기">실습2. component(=composable node) 작성하기</h2>
<h3 id="실습-21-talker-component-작성하기">실습 2.1. talker component 작성하기</h3>
<p>먼저 우리 지난시간에 배웠던 talker를 component로 만들어볼 것이다. 
지난 번 talker와 기능적인 면은 똑같지만 내부적으로 동작하는 방식이 다를 것이다. </p>
<h4 id="실습-211-component-node-코드-작성하기">실습 2.1.1. component node 코드 작성하기</h4>
<ul>
<li><p>패키지 내 src폴더에 다음과 같이 talker_component.cpp라고 파일을 만들어준다.
<img src="https://velog.velcdn.com/images/i_robo_u/post/851622f3-bcff-4b92-8bad-b6371a02f310/image.png" alt=""></p>
</li>
<li><p>talker_component.cpp에 다음과 같은 코드를 작성한다. </p>
<pre><code>// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
</code></pre></li>
</ul>
<p>#include <chrono>
#include <iostream>
#include <memory>
#include <utility></p>
<p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;</p>
<p>using namespace std::chrono_literals;</p>
<p>namespace composition
{</p>
<p>// Create a Talker &quot;component&quot; that subclasses the generic rclcpp::Node base class.
// Components get built into shared libraries and as such do not write their own main functions.
// The process using the component&#39;s shared library will instantiate the class as a ROS node.</p>
<p>  class Talker : public rclcpp::Node
  {
    public:
      Talker(const rclcpp::NodeOptions &amp; options)
      : Node(&quot;talker&quot;, options), count_(0)
      {
        // Create a publisher of &quot;std_mgs/String&quot; messages on the &quot;chatter&quot; topic.
        pub_ = create_publisher&lt;std_msgs::msg::String&gt;(&quot;chatter&quot;, 10);</p>
<pre><code>    // Use a timer to schedule periodic message publishing.
    timer_ = create_wall_timer(1s, std::bind(&amp;Talker::on_timer, this));
  }

  void on_timer()
  {
    auto msg = std::make_unique&lt;std_msgs::msg::String&gt;();
    msg-&gt;data = &quot;Hello World: &quot; + std::to_string(++count_);
    RCLCPP_INFO(this-&gt;get_logger(), &quot;Our talker publishing: &#39;%s&#39;&quot;, msg-&gt;data.c_str());
    std::flush(std::cout);

    // Put the message into a queue to be processed by the middleware.
    // This call is non-blocking.
    pub_-&gt;publish(std::move(msg));
  }

private:
  size_t count_;
  rclcpp::Publisher&lt;std_msgs::msg::String&gt;::SharedPtr pub_;
  rclcpp::TimerBase::SharedPtr timer_;</code></pre><p>  };
}  // namespace composition</p>
<p>#include &quot;rclcpp_components/register_node_macro.hpp&quot;</p>
<p>// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(composition::Talker)</p>
<pre><code>
---

뭔가 어마어마하게 달라진거 같지만 주석을 제외하고 저번에 배운 talker.cpp코드와 상당히 유사한거 같다. 

하나하나 살펴보자. 

![](https://velog.velcdn.com/images/i_robo_u/post/b923e515-3f46-4658-805c-83c375c94c9a/image.png)

- 저번 talker.cpp에서 처럼 c++ 표준 라이브러리에서 헤더파일들을 포함시키고 있다. ros2 프로그램을 작성하기 위해 rclcpp와 std_msgs 패키지에서 헤더파일을 포함시키는 부분도 보인다. 

![](https://velog.velcdn.com/images/i_robo_u/post/b400367e-11bf-4741-bcb1-a98352fa73e5/image.png)

- 좀 더 가독성 높은 시간 변수를 사용하기 위해 chrono_literals를 정의해준다. 

![](https://velog.velcdn.com/images/i_robo_u/post/463bba17-0fdc-48f8-bf36-600a0acd115d/image.png)
- composition이라는 namespace 내에 Talker라는 class를 rclcpp::Node를 상속받아서 만들고 있다. 
- 저번 Talker의 class와 다른 점은 rclcpp::NodeOptions 타입의 options을 생성할 때 인자로 받고 있다는 것이다. 
- 이 options는 Node를 초기화할 때 입력값으로 사용되는데 이는 해당 Talker node가 composition을 구성하는 component가 될 때, 같은composition내 다른 component와 통신이 필요한 경우 데이터를 복사하지 않고 그대로 공유한다는 세팅을 내부적으로 해주기 위해 필요하다. 
- 뭔가 어렵다고 생각하는 사람은 &quot;아 그냥 내부적으로 통신하려면 이렇게 options를 node만들 때 추가해야하는 구나&quot; 라고 받아드리면 된다. 
- 좀 더 자세하게 알고 싶은 사람은 해당 링크에 가서 intra-process communication방식으로 node를 생성할 때 node option이 어떻게 생성되는지 살펴보기 바란다. 
- [intra-process communication링크](https://docs.ros.org/en/humble/Tutorials/Demos/Intra-Process-Communication.html)

![](https://velog.velcdn.com/images/i_robo_u/post/52ad3f22-242e-44bf-ad98-a71377b073a6/image.png)

- Talker class의 생성자를 좀 더 자세히 살펴보면, publisher를 생성한다. 
- 해당 publisher는 &quot;chatter&quot;라는 topic에 std_msgs::msg::String타입의 메세지를 발행한다. 
- 발행해서 Queue에 저장되는 메세지수는 10개로 한다. (구독자가 소모하지 않으면 최대 쌓아둠)
- 1초에 1번씩 on_timer라는 함수를 호출하는 timer를 생성한다.

![](https://velog.velcdn.com/images/i_robo_u/post/545d7e95-e3f1-4aea-be76-579a7012da9c/image.png)

- timer에서 호출되는 callback, on_timer를 정의한다. 
- 해당 callback 함수는 std_msgs::msg::String이라는 타입의 메세지를 정의한다. 
- 해당 메세지변수 data에는 &quot;Hello World&quot;와 count변수를 조합한 문자열이 저장된다. 
- &quot;Our talker publishing: ~&quot;라는 log를 출력한다. 

![](https://velog.velcdn.com/images/i_robo_u/post/1a2380a4-747d-4f43-a591-50279f08ec04/image.png)

- 상단의 클래스 내부에서 사용된 변수들을 정의한다. 
- count_: 이름 그대로 발행될 때마다 증가하는 카운터이다. 
- pub_: 메세지 발생을 담당하는 퍼블리셔이다. 
- timer_: 주기적인 행동을 담당하는 timer이다. 

![](https://velog.velcdn.com/images/i_robo_u/post/9d4aa20f-82be-4764-8667-72e33368cd04/image.png)
- 이부분이 일반 node와 크게 다른 점이다. 
- 저번의 talker.cpp에서는 Talker class를 정의한 후 main함수에서 node를 만들어서 실행했었다. 
- 그러나 component(=composable node)의 경우 rclcpp_components에서 register_node_macro.hpp 헤더파일을 포함시켜
- RCLCPP_COMPONENTS_REGISTER_NODE라는 매크로에 composition::Taker (여기서 composition은 namespace, Taker는 clas이름이다) 를 넣어주어 component를 등록한다. 
- 이렇게 등록하게 되면 나중에 살펴보겠지만 composable node로써 launch파일에서 composition을 만들 때 사용가능하다. 
- 참고로 매크로는 함수가 아닌 무언가를 반복하는 행위를 할 때 작성되어야 하는 텍스트(코드)뭉치를 짧게 정의해놓은 것이라고 이해하면 된다. 

#### 2.1.2. CMakeLists.txt 작성하기 
다음과 같은 코드의 일부를 CMakeLists.txt에 적절히 복사 붙여넣기 한다. 
</code></pre><p>find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(std_msgs REQUIRED)</p>
<p>add_library(talker_component SHARED
  src/talker_component.cpp)
ament_target_dependencies(talker_component
  rclcpp
  rclcpp_components
  std_msgs)
rclcpp_components_register_nodes(talker_component &quot;composition::Talker&quot;)</p>
<pre><code>예시)
![](https://velog.velcdn.com/images/i_robo_u/post/eccfe264-84bf-4c7d-8818-00b22db849b0/image.png)

- rclcpp, std_msgs에 의존하므로 find_package를 통해 찾아준다. 
- component를 등록하기 위해 매크로를 사용했으므로 매크로가 정의된 rclcpp_components패키지를 찾아준다. 
- executable 대신 component는 library형태로 등록해야한다. 
- 따라서 add_library에 library 이름, source코드 위치를 장석해준다. 
- library의 dependency로는 rclcpp, rclcpp_components, std_msgs가 있으므로 추가해준다. 
- &quot;rclcpp_components_register_nodes 함수를 사용해서 talker_component라는 등록할 library(위의 add_library에서 설정한 이름과 같아야함)와 이 library내에 등록할 component의 이름&quot;composition::Talker&quot;를 인자로 넣어 component를 최종적으로 등록한다. 
- composition::Talker는 우리가 cpp파일에 구현할 때 namespace이름 composition과 class 이름 Talker에서 온 것이다. 



#### 2.1.3. pakcage.xml 작성하기
CMakeLists.txt를 작성했으면 package.xml도 적절히 수정해주자. 

다음과 같은 코드를 적절히 복사 붙여넣는다. </code></pre><p>  <build_depend>rclcpp</build_depend>
  <build_depend>rclcpp_components</build_depend>
  <build_depend>std_msgs</build_depend></p>
<p>  <exec_depend>launch_ros</exec_depend>
  <exec_depend>rclcpp</exec_depend>
  <exec_depend>rclcpp_components</exec_depend>
  <exec_depend>std_msgs</exec_depend></p>
<pre><code>
예시)
![](https://velog.velcdn.com/images/i_robo_u/post/7078dce9-9d1a-439d-bbcb-e265b4f03e34/image.png)

- build할 때 필요한 dependency로 rclcpp, rclcpp_components, std_msgs를 추가한다. 
- 실행할 때 필요한 dependency로 rclcpp, rclcpp_components, std_msgs를 추가한다. 


이대로 컴파일 해도 되지만 listener까지 작성하고 compile하도록 하자. 


---
### 실습 2.2. listener component 작성하기
이제 listener를 component로 만들어보자. 
기능은 일반적인 listener와 동일하고 내부적으로 통신한는 방식만 데이터 복사 없이 일어난다는 점이 다르다. 

#### 실습 2.2.1. component 코드 작성하기 
- 패키지 내 src폴더에 다음과 같이 listener_component.cpp라고 파일을 만들어준다.
![](https://velog.velcdn.com/images/i_robo_u/post/8f4e5b77-7c01-470e-8484-6d28a96ebbbb/image.png)


- listener_component.cpp에 다음과 같은 코드를 작성한다.</code></pre><p>// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     <a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.</p>
<p>#include <iostream>
#include <memory></p>
<p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;</p>
<p>namespace composition
{</p>
<pre><code>class Listener : public rclcpp::Node
{
    // Create a Listener &quot;component&quot; that subclasses the generic rclcpp::Node base class.
    // Components get built into shared libraries and as such do not write their own main functions.
    // The process using the component&#39;s shared library will instantiate the class as a ROS node.
    public:
        Listener(const rclcpp::NodeOptions &amp; options)
        : Node(&quot;listener&quot;, options)
        {
        // Create a callback function for when messages are received.
        // Variations of this function also exist using, for example, UniquePtr for zero-copy transport.
        auto callback =
            [this](std_msgs::msg::String::ConstSharedPtr msg) -&gt; void
            {
            RCLCPP_INFO(this-&gt;get_logger(), &quot;Our listener heard: [%s]&quot;, msg-&gt;data.c_str());
            // std::flush(std::cout);
            };

        // Create a subscription to the &quot;chatter&quot; topic which can be matched with one or more
        // compatible ROS publishers.
        // Note that not all publishers on the same topic with the same type will be compatible:
        // they must have compatible Quality of Service policies.
        sub_ = create_subscription&lt;std_msgs::msg::String&gt;(&quot;chatter&quot;, 10, callback);
        }
    private:
        rclcpp::Subscription&lt;std_msgs::msg::String&gt;::SharedPtr sub_;
};</code></pre><p>}  // namespace composition</p>
<p>#include &quot;rclcpp_components/register_node_macro.hpp&quot;</p>
<p>// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(composition::Listener)</p>
<pre><code>
뭐가 많아 보인다. 하나하나 살펴보자. 
![](https://velog.velcdn.com/images/i_robo_u/post/34cb6878-975f-4c60-ae18-955d8968fff0/image.png)

- 필요한 헤더파일을 inlcude한다. 
- c++ 표준 라이브러리에서는 iostream과 memory를 include한다. 
- ros2 프로그래밍을 위해 rclcpp.hpp와 string.hpp 헤더파일을 include한다. 

![](https://velog.velcdn.com/images/i_robo_u/post/b1438789-4571-4992-9169-fbf2d1a8294d/image.png)

- talker_component.cpp에서와 마찬가지로 내부 통신 방법을 정해주기 위해 rclcpp::NodeOptions 타입의 options를 추가해준다. 
- 이 options로 &quot;listener&quot;라는 node를 초기화해준다. 

![](https://velog.velcdn.com/images/i_robo_u/post/2c838fbf-7b3f-438d-b5ae-2693ee71bfb0/image.png)

- lambda 표현을 통해 callback 함수를 정의한다. 
- 이 callback함수는 msg라는 인자를 받아서 msg의 data에 저장되어 있는 값을 출력한다. 
- lambda표현은 이렇게 간단한 함수를 정의할 때 사용하면 용이하다. 
- 그리고 이 callback은 sub_변수에 subscription을 만들 때 같이 등록된다. 
- 해당 subcription은 &quot;chatter&quot; topic에 들어오는 메세지를 구독한다. 
- 메세지 타입은 std_msgs::msg::Sring을 사용한다. 
- 상기 코드에 의하면 구독자는 최대 10개의 메세지를 저장해놓고 차례대로 꺼내 쓸 수 있다. 
- private으로써 subscription, sub_을 정의한다. 

![](https://velog.velcdn.com/images/i_robo_u/post/88f3e453-a130-4b8c-a33a-293a4d94a4cd/image.png)

- talker_component에서와 동일하게 매크로를 사용하여 composition::Listener라는 component를 등록해주는 코드를 작성한다. 


#### 실습 2.2.2. CMakeLists.txt 작성하기 
talker_component처럼 CMakeLists.txt에 library와 component를 추가하는 코드를 작성해주자. 
</code></pre><p>add_library(listener_component SHARED
  src/listener_component.cpp)
ament_target_dependencies(listener_component
  rclcpp
  rclcpp_components
  std_msgs)
rclcpp_components_register_nodes(listener_component &quot;composition::Listener&quot;)</p>
<pre><code>예시) 
![](https://velog.velcdn.com/images/i_robo_u/post/7dd4cfbf-095e-4373-b94d-f6aac7ae7eaa/image.png)

- listener_component라는 library를 추가한다. 
- dependency는 rclcpp, rclcpp_components, std_msgs를 갖는다.
- listener_component라이브러리에 &quot;composition::Listener&quot;라는 component를 추가한다. 
- &quot;composition::Listener&quot;는 listener_component.cpp파일에서 매크로를 통해 추가되었다. 

추가로 install 경로에 해당 library들이 추가 될 수 있게 함수를 다음과 같이 추가한다. </code></pre><p>install(TARGETS
  talker_component
  listener_component
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin)</p>
<pre><code>예시)
![](https://velog.velcdn.com/images/i_robo_u/post/abb76b99-88f5-42fd-bae5-14e52b236c06/image.png)

- talker_component와 listener_component가 추가된 것을 볼 수 있다. 
- 이 library들은 ARCHIVE 키워드 통해서 정적 라이브러리 파일(.a)로 설치된다. 
- LIBRARY 키워드를 통해 공유 라이브러리 파일(.so, .dll등)으로 설치된다. 
- RUNTIME 키워드를 통해 실행파일(.bin)을 설치한다. 

나중에 launch파일로 composition을 만들어서 실행하게 되면 .so 형태의 라이브러리 파일이 사용되는 것을 볼 수 있을 것이다. 

#### 실습 2.2.3. package.xml 작성하기
listener_component는 talker_component와 같은 dependency를 가지고 있으므로 딱히 추가할 dependency는 없다. 

#### 실습 2.2.4. launch파일로 composition 구성하기
자 이제 대망의 composition을 구성할 차례이다. 

component들로 composition을 구성하는 방법은 여러 방법이 있지만 이번 실습에서는 가장 실요적인 launch파일로 composition을 구성하는 방법을 알아볼 것이다. 

먼저 launch파일 경로를 만들어준다. 
</code></pre><p>cd ~/composition_ws/src/simple_composition
mkdir launch
cd launch
code .</p>
<pre><code>
launch폴더 내부에 &quot;simple_components_launch.py&quot;를 만들어준다. 
예시)
![](https://velog.velcdn.com/images/i_robo_u/post/0468dca5-eac9-4b76-bac9-a182edbfa990/image.png)

launch.py에 다음과 같은 코드를 작성한다. </code></pre><h1 id="copyright-2019-open-source-robotics-foundation-inc">Copyright 2019 Open Source Robotics Foundation, Inc.</h1>
<p>#</p>
<h1 id="licensed-under-the-apache-license-version-20-the-license">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</h1>
<h1 id="you-may-not-use-this-file-except-in-compliance-with-the-license">you may not use this file except in compliance with the License.</h1>
<h1 id="you-may-obtain-a-copy-of-the-license-at">You may obtain a copy of the License at</h1>
<p>#</p>
<h1 id="httpwwwapacheorglicenseslicense-20"><a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a></h1>
<p>#</p>
<h1 id="unless-required-by-applicable-law-or-agreed-to-in-writing-software">Unless required by applicable law or agreed to in writing, software</h1>
<h1 id="distributed-under-the-license-is-distributed-on-an-as-is-basis">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</h1>
<h1 id="without-warranties-or-conditions-of-any-kind-either-express-or-implied">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</h1>
<h1 id="see-the-license-for-the-specific-language-governing-permissions-and">See the License for the specific language governing permissions and</h1>
<h1 id="limitations-under-the-license">limitations under the License.</h1>
<p>&quot;&quot;&quot;Launch a talker and a listener in a component container.&quot;&quot;&quot;</p>
<p>import launch
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode</p>
<p>def generate_launch_description():
    &quot;&quot;&quot;Generate launch description with multiple components.&quot;&quot;&quot;
    container = ComposableNodeContainer(
            name=&#39;my_container&#39;,
            namespace=&#39;&#39;,
            package=&#39;rclcpp_components&#39;,
            executable=&#39;component_container&#39;,
            composable_node_descriptions=[
                ComposableNode(
                    package=&#39;simple_composition&#39;,
                    plugin=&#39;composition::Talker&#39;,
                    name=&#39;talker&#39;),
                ComposableNode(
                    package=&#39;simple_composition&#39;,
                    plugin=&#39;composition::Listener&#39;,
                    name=&#39;listener&#39;)
            ],
            output=&#39;screen&#39;,
    )</p>
<pre><code>return launch.LaunchDescription([container])</code></pre><pre><code>코드를 살펴보자

![](https://velog.velcdn.com/images/i_robo_u/post/3d25db83-ab91-45fe-a964-604a2588d304/image.png)

- 먼저 composable node를 사용하기 위해 해당 node들을 담을 container가 필요하다. 
- 이를 위해서 ComposableNodeContainer를 import한다. 
- Composable node를 묘사하기 위해 launch_ros.descriptions에서 ComposableNode를 import한다. 

![](https://velog.velcdn.com/images/i_robo_u/post/62c2d791-596d-48d3-ae99-e0dbd2addae3/image.png)

- composition을 만들기 위해서는 container가 필요하다. 이 container는 rclcpp_component pakcage에 구현되어 있는데 위와 같이 executable로 &#39;component_container&#39;를 사용하면된다. 
- 그후 component로 사용할 plugin들을 ComposableNode로써 생성해주면된다. 
- 해당 코드의 경우 composition::Talker와 composition::Listener가 사용되었다. 

이렇게 component로 추가해놓은 talker와 listener를 사용해서 launch파일에서 composition을 구성했다. 

그럼 CMakeLists.txt에서 launch파일을 추가하고 compile해보자. 
</code></pre><h1 id="install-launch-files">Install launch files.</h1>
<p>install(DIRECTORY
  launch
  DESTINATION share/${PROJECT_NAME}
)</p>
<pre><code>예시)
![](https://velog.velcdn.com/images/i_robo_u/post/7dd02a90-5a6c-4259-a4f2-b18e1d61acf4/image.png)


---
## 실습3. 빌드 및 실행하기
파일들의 변경사항을 저장했는지 확인하고 빌드 및 실행해보자. 

- colcon build</code></pre><p>cd ~/composition_ws/
colcon build</p>
<pre><code>
- 실행하기(다른 터미널 여는 것을 권장)</code></pre><p>cd ~/composition_ws/
source install/setup.bash
ros2 launch simple_composition simple_components_launch.py</p>
<pre><code>예시) 
![](https://velog.velcdn.com/images/i_robo_u/post/8dafa3dc-cfc9-4b89-b7f7-7efb6cc36075/image.png)

실행 예시를 보면 몇가지 주목 할만한 메세지들이 있다. 
- libtalker_component.so와 liblilstener_component.so형태로 library가 load된 것을 확인할 수 있다. 
- /talker와 /listener node는 /my_container에 node로 load된 것을 확인 할 수 있다. 
- /talker는 &quot;Our talker publishing~&quot;하고 /listener는 &quot;Our listener heard~&quot;라고 동작하는 것을 확인 할 수 있다. 

---
# 요약
오늘 내용은 새로운 개념을 배워야 하기 때문에 살펴볼게 정말 많았다. 

그러나 핵심은 간단하기 때문에 두려워하지말자!

&lt;핵심요약&gt;
- component(=composable node)는 composition을 구성하는 node이다. 
- composition을 사용하는 주된 이유는 방대한 양의 데이터(이미지 정보등) 복사 없이 효율적인 통신을 하기 위함이다. 
- component node로 만들 때는 node를 정의할 때 NodeOptions가 필요했다. 
- component를 만들기 위해서는 main함수 대신 component를 등록한다는 매크로가 필요하다. 
- component를 library로 등록하기 위해 CMakeLists.txt와 package.xml를 적절히 수정해줘야한다. 
- launch파일에서 component들로 composition을 구성하는 것이 가능하다. 

요약해보니 6줄 밖에 안된다. 별거 없는 듯 하다. 
앞으로 방대한 데이터의 교환이 필요할 때 composition을 통해 구현해보자. 
대신 구현할 때 기본적인 composition구조를 갖추고 내부 상세 알고리즘을 작성하는 습관은 디버깅 지옥에 빠지는 것을 예방해주니 꼭 기억하도록 하자!

다음 시간에는 node 상태를 관찰하고 변경하는 lifecycle에 대해 배워볼 예정이다! 

처음엔 어렵지만 많이 유용하니 꼭 공부해주길 바란다~!

---
질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!

문의메일: irobou0915@gmail.com

오픈톡 문의: https://open.kakao.com/o/sXMqcQAf

[IRoboU 유튜브 채널](https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg)

참고 문헌: 
- [writing a composable node](https://docs.ros.org/en/humble/Tutorials/Intermediate/Writing-a-Composable-Node.html)

- [composing multiple nodes in a single process](https://docs.ros.org/en/humble/Tutorials/Intermediate/Composition.html)

- [composition](https://docs.ros.org/en/humble/Concepts/Intermediate/About-Composition.html)

- [base code - talker](https://github.com/ros2/demos/blob/humble/composition/src/talker_component.cpp)
- [base code - listener](https://github.com/ros2/demos/blob/humble/composition/src/listener_component.cpp)

---
![](https://velog.velcdn.com/images/i_robo_u/post/51be838d-48fa-4055-b80d-de2edbe99107/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[그럼 node 특성을 결정짓는 parameter는 코드로 어떻게 짤까?]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B7%B8%EB%9F%BC-node-%ED%8A%B9%EC%84%B1%EC%9D%84-%EA%B2%B0%EC%A0%95%EC%A7%93%EB%8A%94-%EC%BD%94%EB%93%9C%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%A7%A4%EA%B9%8C</link>
            <guid>https://velog.io/@i_robo_u/%EA%B7%B8%EB%9F%BC-node-%ED%8A%B9%EC%84%B1%EC%9D%84-%EA%B2%B0%EC%A0%95%EC%A7%93%EB%8A%94-%EC%BD%94%EB%93%9C%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%A7%A4%EA%B9%8C</guid>
            <pubDate>Sun, 25 Feb 2024 00:24:57 GMT</pubDate>
            <description><![CDATA[<p>ROS2에서 parameter는 어떻게 코드로 구현할 수 있을까?</p>
<p>오늘은 그 비밀에 대해 파헤쳐보자!</p>
<hr>
<p>&lt;목표&gt;
C++로 class(node)내에 parameter를 정의하고 실행하는 실습을 진행해본다. </p>
<p>&lt;예상 소요 시간&gt;
약 20분</p>
<p><a href="https://youtu.be/09fQ4EOLKLk">&lt;영상 tutorial&gt;</a></p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>지난 시간의 NAVER LABS의 자율주행 로봇에서 수많은 node를 켤 수 있는 launch파일에 대해 설명했었다.(<a href="https://www.youtube.com/watch?v=UXJoA6LfJEg">관련 영상</a>)
이렇게 launch파일로 node를 실행할 때 node의 기능이 내가 정한 형태로 동작하게 하고 싶을 때가 있다.</p>
<p>이는 parameter라는 것으로 조절할 수 있는 경우가 많다. </p>
<p>예를 들어 자율주행 로봇의 이동속도나 장애물을 얼마나 멀리로부터 감지해서 피할 것인지 등이 parameter에 해당하며 이 외에도 수많은 파라미터를 로봇은 가지고 있다. </p>
<p>이번 시간에는 이런 parameter를 C++ class(node 생성을 담당하는 class)에서 구현해보고 launch파일에서 어떻게 설정해주는지 실습해보자. </p>
<hr>
<h1 id="준비물">준비물</h1>
<ul>
<li><a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Parameter-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-5">파라미터 개념</a></li>
</ul>
<hr>
<h1 id="실습">실습</h1>
<h2 id="실습-1-패키지-생성하기">실습 1. 패키지 생성하기</h2>
<p>&#39;cpp_paramters&#39;라는 이름을 갖고 rclcpp에 의존하는 package를 하나 생성하자. </p>
<pre><code>cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_parameters --dependencies rclcpp</code></pre><p>해당 패키지는 이번 시간에 우리가 parameter를 구현하고 설정하는데 사용할 예정이다. </p>
<hr>
<h3 id="실습-11-pakcagexml-수정해주기">실습 1.1. pakcage.xml 수정해주기</h3>
<p>지금 단계에서 필수는 아니지만 배포(누군가 내 패키지를 사용하게 뿌리는 행위)할 때를 대비해서 package.xml에 들어가서 description과 maintainer, license등을 다음과 같이 변경해주자. </p>
<pre><code>&lt;description&gt;C++ parameter tutorial&lt;/description&gt;
&lt;maintainer email=&quot;you@email.com&quot;&gt;Your Name&lt;/maintainer&gt;
&lt;license&gt;Apache License 2.0&lt;/license&gt;</code></pre><p>maintainer의 이름은 적절히 본인의 email을 작성하면 된다. </p>
<hr>
<h3 id="실습-2-c-node-작성하기">실습 2. C++ node 작성하기</h3>
<p>이제 본격적으로 C++ node를 작성해보자. </p>
<p>먼저 cpp_parameters_node.cpp파일을 패키지내 src 경로에 생성한다. </p>
<pre><code>cd ~/ros2_ws/src/cpp_parameters/src
gedit cpp_parameters_node.cpp</code></pre><p>cpp_parameters_node.cpp에 다음과 같은 코드를 작성하도록 하자. </p>
<pre><code>#include &lt;chrono&gt;
#include &lt;functional&gt;
#include &lt;string&gt;

#include &lt;rclcpp/rclcpp.hpp&gt;

using namespace std::chrono_literals;

class MinimalParam : public rclcpp::Node
{
public:
  MinimalParam()
  : Node(&quot;minimal_param_node&quot;)
  {
    this-&gt;declare_parameter(&quot;my_parameter&quot;, &quot;world&quot;);

    timer_ = this-&gt;create_wall_timer(
      1000ms, std::bind(&amp;MinimalParam::timer_callback, this));
  }

  void timer_callback()
  {
    std::string my_param = this-&gt;get_parameter(&quot;my_parameter&quot;).as_string();

    RCLCPP_INFO(this-&gt;get_logger(), &quot;Hello %s!&quot;, my_param.c_str());

    std::vector&lt;rclcpp::Parameter&gt; all_new_parameters{rclcpp::Parameter(&quot;my_parameter&quot;, &quot;world&quot;)};
    this-&gt;set_parameters(all_new_parameters);
  }

private:
  rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared&lt;MinimalParam&gt;());
  rclcpp::shutdown();
  return 0;
}</code></pre><hr>
<h3 id="21-코드-분석">2.1. 코드 분석</h3>
<ul>
<li>항상 그렇듯이 처음에 cpp가 필요로 하는 package와 library를 include해준다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/be1c17de-9e50-472b-948e-ffa9e091bab9/image.png" alt=""></li>
</ul>
<ul>
<li><p>(참고) chrono, functional, string은 C++표준 라이브러리에 속하는 헤더파일이다. rclcpp는 ROS2 패키지이며 rclcpp/rclcpp.hpp는 패키지 내 library의 헤더파일에 해당한다. </p>
</li>
<li><p>std::chrono_literals라는 namespace를 지정해서 1000ms(1000밀리 세컨드, 1초)등의 표현을 사용한다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/16d6baaa-c0c3-4f87-9259-046a6e5aa384/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><p>class 생성자(node를 생성을 담당하는 부분)에서 &quot;declare_parameter&quot;를 통해 &quot;my_parameter&quot;라는 파라미터를 기본값 &quot;world&quot;로 생성한다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/673cab45-efe4-4489-b335-d9496bc407eb/image.png" alt=""></p>
</li>
<li><p>그다음 타이머를 생성하고 1000ms(1초)에 한번씩 주기적으로 timer_callback이라는 함수가 호출될 수 있도록 설정해준다. timer_callback은 하단에 따로 정의된다.  </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/98df121c-c55a-4ff3-a00b-eb14c715eb98/image.png" alt=""></p>
<ul>
<li><p>이제 timer_callback을 정의할 차례이다. parameter를 구현하는데 필수는 아니지만 이 실습에서는 paramter값으로 우리가 어떤 기능을 구현할 수 있는지 보여주기 위해 구현된 부분이라고 생각하면된다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/0eab6298-19f0-4cb0-b965-2af1d64fc4d8/image.png" alt=""></p>
</li>
<li><p>timer_callback에서는 다음과 같은 동작이 수행된다. </p>
</li>
</ul>
<ol>
<li>&quot;my_parameter&quot;의 파라미터 값을 획득해 my_param이라는 변수에 저장한다. </li>
<li>이 변수가 정확하게 획득됐는지 확인차 log를 띄운다. </li>
<li>set_paramters라는 함수로 parameter을 세팅해주기위해서 먼저 all_new_paramters라는 벡터타입의 변수를 선언한다. </li>
<li>벡터의 첫 번째 원소값은 rclcpp::Paramter타입이며 이 타입은 파라미터 이름과 그 파라미터의 값을 이용해서 초기화 될 수 있다. </li>
<li>즉 &quot;all_new_paramters안에 my_parameter라는 파라미터는 world라는 값을 가져&quot;라고 말해주는 vector가 정의됐다고 생각하면된다. </li>
<li>이 vector를 이용해서 ser_paramters로 &quot;my_prameter&quot;의 값을 world로 만들어준다. </li>
</ol>
<p>이때 의문이 들 수 있는데, &quot;아니 이 행위를 왜하는거지?&quot;라는 생각이 들 수 있다. </p>
<p>이따 실습에서 보겠지만 &quot;my_paramter&quot;라는 파라미터의 값이 혹시 사용자에 의해 외부에서 바뀔시 &quot;world&quot;라는 값으로 다시 바꿔버리기 위해 있는 코드이다. </p>
<p>실제 실습에서 확인하기로 하고 나머지 코드를 분석하자. </p>
<ul>
<li><p>다음은 private로 timer를 선언해준다. 위 코드에서 사용하기 위함이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/1a7eeda9-199e-424c-80f7-73364a653d85/image.png" alt=""></p>
</li>
<li><p>마지막으로 main함수에서 우리가 정의한 node가 실행될 수 있도록 객체화하는 동시에 spin으로 node가 켜져있을 수 있도록 코드를 작성한다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/b7313a5b-c5b0-4558-a5e0-63be8016015e/image.png" alt=""></p>
</li>
<li><p>Ctrl+C와 같은 중지 명령이 들어오면 node를 shutdown하고 0을 반환 후 프로그램을 종료한다. </p>
</li>
</ul>
<hr>
<h4 id="211-선택사항-add-paramterdescriptor">2.1.1. (선택사항) Add ParamterDescriptor</h4>
<p>조금 번거롭긴하지만 해놓으면 누군가 내가 생성한 paramter를 사용할 때 도움이 될 수 있는 코드 기능이 있다. </p>
<p>바로  내가 작성한 paramter가 어떤 역할을 담당하는지 묘사하는 설명을 달아 놓는 것이다. </p>
<p>예를 들어 다음과 같은 코드를 살펴보자. </p>
<pre><code>// ...

class MinimalParam : public rclcpp::Node
{
public:
  MinimalParam()
  : Node(&quot;minimal_param_node&quot;)
  {
    auto param_desc = rcl_interfaces::msg::ParameterDescriptor{};
    param_desc.description = &quot;This parameter is mine!&quot;;

    this-&gt;declare_parameter(&quot;my_parameter&quot;, &quot;world&quot;, param_desc);

    timer_ = this-&gt;create_wall_timer(
      1000ms, std::bind(&amp;MinimalParam::timer_callback, this));
  }</code></pre><p>위의 코드는 우리가 실습 2.에서 작성한 코드와 거의 유사하지만 자세히 보면 </p>
<pre><code>    auto param_desc = rcl_interfaces::msg::ParameterDescriptor{};
    param_desc.description = &quot;This parameter is mine!&quot;;</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/45fbd7f6-ae43-4457-92e4-1f4bcd95fc7d/image.png" alt=""></p>
<p>해당 부분이 추가로 작성되어 있고 declare_paramter에서도 param_desc가 추가된 것을 볼 수 있다. 
이는 &quot;my_paramter&quot;에 대한 설명을 추가한 것이라고 보면 된다. </p>
<p>이렇게 설명을 추가해놓으면 나중에 다음과 같은 명령어를 사용하여 터미널에서 파라미터의 설명을 확인할 수 있다. </p>
<pre><code>ros2 param describe /minimal_param_node my_parameter</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/b71c6c82-0783-4a80-a33a-f593946058f3/image.png" alt=""></p>
<hr>
<h3 id="22-executable-추가">2.2. executable 추가</h3>
<p>코드 작성도 끝났겠다 이제 CMakeLists.txt 파일에 적절히 executable을 추가해주자. </p>
<pre><code>add_executable(minimal_param_node src/cpp_parameters_node.cpp)
ament_target_dependencies(minimal_param_node rclcpp)

install(TARGETS
    minimal_param_node
  DESTINATION lib/${PROJECT_NAME}
)</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/4b0cc8ad-2f6f-4f9c-af5f-36018304d604/image.png" alt=""></p>
<hr>
<h2 id="실습-3-빌드-및-실행">실습 3. 빌드 및 실행</h2>
<p>이제 빌드하고 실행해보자</p>
<ul>
<li><p>먼저 혹시 빠뜨린 ros dependency가 있는지 체크해서 설치한다. </p>
<pre><code>cd ~/ros2_ws
rosdep install -i --from-path src --rosdistro humble -y</code></pre></li>
<li><p>우리 패키지를 빌드한다. </p>
<pre><code>cd ~/ros2_ws
colcon build --packages-select cpp_parameters</code></pre></li>
<li><p>현재 작업공간의 설치된 executable를 소스하고 실행하여 결과를 확인한다. </p>
<pre><code>cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_parameters minimal_param_node</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/d546356d-4d9d-4d2a-a023-635c656fabeb/image.png" alt="">
1초마다 my_parameter로부터 world를 읽어 log를 출려하는 것을 볼 수 있다. *<em>단, log만 터미널에 보여줄 뿐 talker처럼 publish하는 것은 아니니 혼동하지 않도록 하자. *</em></p>
<hr>
<h3 id="실습-31-터미널에서-parameter-변경하기">실습 3.1. 터미널에서 parameter 변경하기</h3>
<p>자 그러면 아까 timer_callback에서 world로 값을 왜 설정하게 했는지 이유와 관련된 추가 실습을 진행해보자. </p>
<p>터미널에서 node내 파라미터 값을 변경해주자. 
먼저 ros2 param list 명령어를 통해 파라미터가 정상적으로 존재하는지 확인한다. </p>
<pre><code>ros2 param list</code></pre><p>결과 
<img src="https://velog.velcdn.com/images/i_robo_u/post/cafc9aeb-8b4a-482b-9c14-299b3ec4057e/image.png" alt=""></p>
<p>파라미터도 있겠다 이 값을 이번엔 earth로 변경해보자. 
파라미터를 터미널에서 설정할 땐 다음과 같은 명령어를 사용한다. (상단의 파라미터 개념 포스트 및 영상 참고) </p>
<pre><code>ros2 param set /minimal_param_node my_parameter earth</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/fa3e73d5-2049-4477-81af-6a89d65823dd/image.png" alt=""></p>
<p>그런데 한가지 이상한 것이 earth로 바꼈다가 다시 world로 돌아온 것을 볼 수 있다. </p>
<p>이는 아까 우리가 timer callback에서 사용자가 임의로 파라미터 값을 수정할 것을 대비해 원래 값인 world로 돌려놓도록 코딩했기 때문이다. </p>
<p>이렇게 parameter를 구현하고 코드내에서 해당 파라미터를 이용해서 작업을 할 수 있는 간단한 예시를 보여준다고 보면된다. </p>
<hr>
<h3 id="32-launch파일로-변경하기">3.2. launch파일로 변경하기</h3>
<p>저번 launch파일을 작성하는 시간(<a href="https://velog.io/@i_robo_u/%EB%8D%94-%EC%89%BD%EA%B2%8C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89%ED%95%98%EC%84%B8%EC%9A%94.-ROS2-Launch-%ED%8C%8C%EC%9D%BC-%EA%B3%B5%EB%9E%B5%ED%95%98%EA%B8%B0">관련 포스트 및 영상</a>)에서도 배웠지만 다양한 기능을 하는 로봇인 경우엔 node수도 많고 해당 node들의 paramter양도 어마어마하다. </p>
<p>따라서 ros2 param set 명령어로 설정하는건 엄청난 시간이 필요하기 때문에 launch파일을 이용해서 한번에 미리 설정해놓은 값을 계속 사용할 수 있다. </p>
<p>먼저 launch 경로를 패키지 내에 만든다. 경로 내에 cpp_paramters_launch.py를 만든다. </p>
<pre><code>cd ~/ros2_ws/src/cpp_paramters
mkdir launch
cd launch
gedit cpp_parameters_launch.py</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/e5eb2bd9-cbbf-4137-b2f5-2289c22b4799/image.png" alt=""></p>
<p>다음과 같은 코드를 붙여넣자. </p>
<pre><code>from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package=&quot;cpp_parameters&quot;,
            executable=&quot;minimal_param_node&quot;,
            name=&quot;custom_minimal_param_node&quot;,
            output=&quot;screen&quot;,
            emulate_tty=True,
            parameters=[
                {&quot;my_parameter&quot;: &quot;earth&quot;}
            ]
        )
    ])</code></pre><p>간단히 코드를 분석해보자 </p>
<ul>
<li>cpp_parameters 패키지 안에서 minimal_param_node라는 executable를 실행할 건데 custom_minimal_param_node라는 node이름으로 실행해줘 log 메세지 같은 output은 &quot;screen&quot;(터미널)에 띄어줘</li>
<li>emulate_tty=True 는 터미널에 node의 출력을 표시하기 위해 사용된다. </li>
<li>parmeters 리스트안에 map 타입으로 my_paramter의 값을 earth로 정의하고 있다.  <pre><code>parameters=[
              {&quot;my_parameter&quot;: &quot;earth&quot;}
          ]</code></pre></li>
</ul>
<p>이렇게 launch파일을 작성했으면 CMakeLists.txt에 가서 launch 폴더 내용물들이 share폴더에 설치되도록 설정해준다. </p>
<pre><code>install(
  DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/04877f86-41a0-4302-ba43-4e9e636255db/image.png" alt=""></p>
<p>colcon build하고 실행해준다. </p>
<pre><code>cd ~/ros2_ws
colcon build --packages-select cpp_parameters
source install/setup.bash
ros2 launch cpp_parameters cpp_parameters_launch.py</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/363b0b6a-460e-4714-a1c2-3b36da6d0eb5/image.png" alt=""></p>
<p>launch파일에 의해 실행될 때 최초에 node가 생성될 때 earth로 세팅되었다가 timer_callback에서 다시 world로 값이 변경되는 것을 확인 할 수 있다. </p>
<hr>
<h1 id="요약">요약</h1>
<p>오늘은 node내에서 parameter를 정의하고 그 값을 어떻게 획득 및 세팅하는지에 해에 알아보았다. </p>
<p>그 흐름을 정리하자면 다음과 같다. </p>
<ul>
<li>node를 선언할 때 declare_parameter를 사용해 원하는 paramter를 정의한다. 정의할 때 파라미터 이름과 기본값을 설정할 수 있다. </li>
<li>get_parameter함수로 node 내 원하는 파라미터 값을 획득할 수 있다. </li>
<li>set_parameters함수와 std::vector<a href="rclcpp::Parameter">rclcpp::Parameter</a> 타입을 이용해 node내 파라미터들을 변경할 수 있다. </li>
</ul>
<p>구현 외에 파라미터 설정 방법은 두가지가 있었다. </p>
<ul>
<li>터미널에서 ros2 param set 명령어를 이용한다. </li>
<li>ros2 launch파일을 작성할 때 파라미터 값을 설정해준다. </li>
</ul>
<p>로봇 개발에서 기능을 구현할 때(node를 만들때) 꼭 필요한 parameter개념이므로 익혀두면 두고두고 도움될 것이다. </p>
<p>문법을 하나하나 외운다기보다는 역시 흐름을 기억하고 항상 참고할 코드를 따로 준비해두도록 하자. </p>
<p>다음 시간에는 방대한 데이터를 처리해야하는 로봇개발에서 유용하게 사용될 수 있는 component(composable node)에 대한 기초 개념을 배워볼 예정이다. </p>
<hr>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p>오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a></p>
<p><a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">IRoboU 유튜브 채널</a></p>
<p><a href="https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Using-Parameters-In-A-Class-CPP.html">참고 문헌</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/4a12174b-964e-4e7f-97ed-85501b86a0a6/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[더 쉽게 프로그램 실행하세요. ROS2 Launch 파일 공략하기]]></title>
            <link>https://velog.io/@i_robo_u/%EB%8D%94-%EC%89%BD%EA%B2%8C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89%ED%95%98%EC%84%B8%EC%9A%94.-ROS2-Launch-%ED%8C%8C%EC%9D%BC-%EA%B3%B5%EB%9E%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@i_robo_u/%EB%8D%94-%EC%89%BD%EA%B2%8C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89%ED%95%98%EC%84%B8%EC%9A%94.-ROS2-Launch-%ED%8C%8C%EC%9D%BC-%EA%B3%B5%EB%9E%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 12 Feb 2024 05:08:28 GMT</pubDate>
            <description><![CDATA[<p>여태까지 ros2 run으로만 c++ executable를 실행했었다. 커맨드를 일일이 다 치는 것도 너무 번거롭다. 더 쉬운 방법이 없을까?</p>
<p>오늘은 ros2 run보다 더 많은 기능을 제공하는 ros2 launch에 대해 배워보자. </p>
<hr>
<p>&lt;목표&gt;
ROS2 package의 executable을 실행할 수 있는 launch파일을 작성해본다. </p>
<p>&lt;예상 소요 시간&gt;
약 10분</p>
<p>&lt;영상 tutorial&gt;
<a href="https://youtu.be/UXJoA6LfJEg">링크텍스트</a></p>
<hr>
<h1 id="준비물">준비물</h1>
<ul>
<li><a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0">ROS2 패키지 생성 실습</a></li>
</ul>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>launch파일은 한번에 단일 node만 실행할 수 있는 ros2 run과는 다르게 여러 node들을 실행 할 수 있게 해주며 node의 특징을 결정 짓는 parameter도 정의할 수 있다. </p>
<p>이 밖에도 더 여러 기능이 있지만 일단 node들을 구성하고 관리하는데 도움을 주는 파일정도라고 기억하고 있으면 된다. </p>
<hr>
<h1 id="실습">실습</h1>
<h2 id="실습-1-package-생성">실습 1. package 생성</h2>
<p>먼저 launch파일을 위치시킬 package를 생성하자. </p>
<ul>
<li>workspace와 package만들기<pre><code>cd ~/
mkdir -p launch_ws/src
cd launch_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_launch_example</code></pre></li>
</ul>
<h2 id="실습-2-lauch-파일를-위한-경로-구성">실습 2. lauch 파일를 위한 경로 구성</h2>
<p>일반적으로 ROS2의 launch파일은 패키지내 launch라는 경로에 위치한다. 
우선 launch라는 폴더를 다음과 같이 패키지내 생성해주자. </p>
<pre><code>cd ~/launch_ws/src/cpp_launch_example
mkdir launch</code></pre><p>(결과 예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/6448b4c8-1e04-46fa-ae28-e90ecfee7a81/image.png" alt=""></p>
<p>추가로 ROS2는 install폴더에 설치된 파일들에 접근해서 프로그램을 실행하는데, 이 작업을 CMakeLists.txt에서 다음과 같이 launch폴더를 추가함으로써 install경로에 launch 폴더를 설치할 수 있다.  </p>
<ul>
<li>CMakelist.txt 하단에 추가할 내용. 단, ament_package()라는 함수 보단 위에 추가해야한다. <pre><code># Install launch files.
install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}/
)</code></pre></li>
</ul>
<p>(visual code editor에서 추가한 결과 예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/a5bd55d4-fed3-4465-84e6-d9e6c4839425/image.png" alt=""></p>
<h2 id="실습-3-launch파일-작성하기">실습 3. launch파일 작성하기</h2>
<p>이제 launch파일을 작성해야하는데 몇가지 참고해야하는 규칙이있다. </p>
<ul>
<li>launch파일 이름은 마음대로 정해도된다.</li>
<li>그렇지만 ros2 launch라는 명령어를 사용하기 위해서 launch 파일 이름에 launch.py를 넣어줘야한다. </li>
</ul>
<p>위의 규칙에 따라 my_script_launch.py라는 launch 파일을 만들자. </p>
<ul>
<li>gedit을 이용해서 launch파일 생성하는 법</li>
</ul>
<ol>
<li>launch파일 생성</li>
</ol>
<pre><code>cd ~/launch_ws/src/cpp_launch_example/launch
gedit my_script_launch.py</code></pre><ol start="2">
<li>my_script_launch.py에 다음 내용을 추가<pre><code>import launch
import launch_ros.actions
</code></pre></li>
</ol>
<p>def generate_launch_description():
    return launch.LaunchDescription([
        launch_ros.actions.Node(
            package=&#39;demo_nodes_cpp&#39;,
            executable=&#39;talker&#39;,
            name=&#39;talker&#39;),
  ])</p>
<pre><code>
- visual code로 만드는 법
1. new file을 클릭하여 my_script_launch.py를 생성
![](https://velog.velcdn.com/images/i_robo_u/post/5555d1c6-056b-4fd0-842c-1273e91b660b/image.png)

2. my_script_launch.py에 다음 내용을 추가</code></pre><p>import launch
import launch_ros.actions</p>
<p>def generate_launch_description():
    return launch.LaunchDescription([
        launch_ros.actions.Node(
            package=&#39;demo_nodes_cpp&#39;,
            executable=&#39;talker&#39;,
            name=&#39;talker&#39;),
  ])</p>
<pre><code>(결과 예시)
![](https://velog.velcdn.com/images/i_robo_u/post/a51fc987-9ccd-4ea7-9409-f156c6ab97fe/image.png)

### 실습 3.1. 코드 분석
위와 같이 launch파일을 작성하기 위해서 필요로 하는 요소는 다음과 같다.
- generate_launch_description()이라는 함수 정의하기
- launch.LaucnDescription()이라는 객체를 return하기
- launch.LaucnDescription()객체내에 launch_ros.actions.Node를 이용해 실행하고자 하는 Node 정보를 추가해주기
- launch.LaucnDescription()객체 인자로 대괄호를 사용하고 있으므로 launch_ros.actions.Node형태의 여러 노드가 올 수 있는 것을 참고
-이때 launch_ros.actions.Node에는 package 정보, executable 정보, node name정보 등이 포함되어 있어야한다. 

따라서 위의 launch파일을 정리해보면
&#39;demo_nodes_cpp&#39;라는 package에서 &#39;talker&#39;라는 executable를 &#39;talker&#39;라는 node 이름을 갖도록 실행한다고 작성한 것이다. 

## 실습 4. launch 파일 빌드하기 
lauch파일을 작성했으면 workspace에서 colcon build해주자</code></pre><p>cd ~/launch_ws
colcon build</p>
<pre><code>성공적으로 빌드했으면 ros2 launch를 이용해 다음과 같이 launch파일을 실행해준다. 
</code></pre><p>cd ~/launch_ws
source install/setup.bash
ros2 launch cpp_launch_example my_script_launch.py</p>
<pre><code>
정상적으로 실행되면 다음과 같이 terminal에 메세지가 뜨는 것을 관찰 할 수 있다. 
![](https://velog.velcdn.com/images/i_robo_u/post/2de0288f-d130-4447-9b1a-ca3c8f0aac5a/image.png)

## 참고사항
이번에 실행했던 프로그램은 굉장히 간단했기 때문에 launch.py 파일도 굉장히 단순했다. 그렇지만 좀 더 많은 node를 구성하고 관리하는 경우 더 복잡한 launch.py를 작성해야한다. 

그렇지만 아직까지 우리들이 다루는 실습은 그렇게 복잡한 launch파일을 필요로 하지 않으므로 일단 한개의 node를 실행하는 lauch파일을 작성하는 배우는 법에 대해서만 배우도록 하고 나중에 더 고도화된 프로그램을 실행할 때 더 자세히 배워보도록 하자. 

---
오늘은 간단한 node를 실행할 수 있는 launch.py 작성법에대해 알아봤다. 

다시 한번 launch.py를 작성할 때 들어가야 하는 요소를 정리하면 다음과 같다. 

**launch.py 작성 방법 요약**
- launch파일 이름이 꼭 launch.py로 끝나야 ros2 launch에서 인식가능함.
- packag이름
- pakcag내 executable 이름
- executable의 node이름
- 더 복잡할 경우 다른 정보도 필요하지만 필요하면 배우는 걸로!


import를 하고 generate_launch_description()같은 이름긴 함수들이 많아 복잡해보이지만 launch파일을 만들 때 반복 되는 부분이라 자기 나름의 예시 launch.py를 작성해서 잘 저장해놨다가 필요할 때 보고 새로운 launch.py를 작성하면된다. 

그러니 함수나 객체 이름을 외울 걱정은 No! 대신 들어가야하는 구성들을 위의 요약처럼 흐름을 외워두도록 하자!

다음 시간에는 ROS2의 더 어려운 개념을 배워보거나 여태까지 배운 내용들에 대한 연습문제를 다뤄볼 예정이다. 

---

오늘도 여기까지 온 스스로를 칭찬해주자!

질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!


문의메일: irobou0915@gmail.com

오픈톡 문의: https://open.kakao.com/o/sXMqcQAf

[IRoboU 유튜브 채널](https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg)

[참고 문헌](https://docs.ros.org/en/humble/Tutorials/Intermediate/Launch/Launch-system.html)


![](https://velog.velcdn.com/images/i_robo_u/post/d0e9b5ba-2ca8-450b-8ed8-8e1a6df464dd/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[그래서 어떻게 대화할건데?? ROS2 내 msg와 srv 만들기]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B7%B8%EB%9E%98%EC%84%9C-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8C%80%ED%99%94%ED%95%A0%EA%B1%B4%EB%8D%B0-ROS2-%EB%82%B4-msg%EC%99%80-srv-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@i_robo_u/%EA%B7%B8%EB%9E%98%EC%84%9C-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8C%80%ED%99%94%ED%95%A0%EA%B1%B4%EB%8D%B0-ROS2-%EB%82%B4-msg%EC%99%80-srv-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Fri, 09 Feb 2024 06:51:27 GMT</pubDate>
            <description><![CDATA[<p>지지난 시간과 지난시간에 topic의 publisher &amp; subscriber 그리고 service의 server와 client를 직접 코드로 어떻게 구현하는지 알아봤다. </p>
<p>그런데 저번에는 이미 존재하는 msg와 srv파일을 사용했는데 </p>
<p>로봇개발을 하다보면 나만의 msg와 srv를 만들고 싶을 때가 있다. </p>
<p>내가 직접 필요한 형태를 정의할 수 있을까?</p>
<p>물론 만들 수 있다! 오늘은 이 파일들을 어떻게 직접 만드는지에 대해 실습해보자. </p>
<hr>
<p>&lt;목표&gt;
나만의 interface file(msg와 srv)파일을 정의하고 이를 사용하는 c++ node를 작성한다. </p>
<p>&lt;예상 소요 시간&gt;
20분</p>
<p>&lt;동영상 tutorial&gt;
<a href="https://youtu.be/VqvyZcLsJ08">유튜브 링크</a></p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>저번에 service server와 client를 설명할 때 짜장면에 비유했던 것 처럼 고객(client)가 짜장면을 주문할 때 메뉴판을 보고 주문서에(srv파일에서 request) 적어서 주인(server)에게 service를 호출(call)한다고 했다. </p>
<p>모든 중국집이 같은 메뉴와 메뉴판을 사용한다면 미리 만들어진 메뉴판을 사용하면 되지만 새로운 메뉴가 추가 될 경우 새로운 메뉴판을 만들어야 할 것이다. </p>
<p>이렇게 로봇개발을 하다보면 누군가 만들어놓은(pre-defined) msg나 srv를 사용하는 경우가 많지만 가끔은 나만의 msg와 srv를 만들고 싶을 때가 있다. </p>
<p>예를 들어서 카메라 이미지를 subscribe해서 이미지내 사물들을 publish하는 Tesla의 자율주행 자동차가 있다고 하자. 
이때 publish되는 msg에 사물 갯수, 사물의 이름을 담고 있는 list를 담고 싶은데 미리 정의된 msg가 없을 수 있다. </p>
<p>어떻게 할지 막막하지만 사실 ROS2는 자신만의 msg와 srv를 만들 수 있어서 그냥 내 나름대로 필요한 정보들을 정의해서 만들면된다. </p>
<p>이렇게 직접 만든 msg와 srv를 ROS2에서는 custom interfaces라고 한다. </p>
<p>이는 interface가 일반적으로 두개의 무언가를 연결하는 접점이라는 뜻인데 msg는 publisher와 subscriber를 srv는 service server와 client를 연결해준다점에 초점을 둬 이름을 지었다고 생각하면 기억하기 쉽다. </p>
<hr>
<h1 id="준비물">준비물</h1>
<ul>
<li>ROS2 workspace</li>
<li><a href="https://velog.io/@i_robo_u/ROS2%EC%9D%98-%EA%BD%83-publisher%EC%99%80-subscriberC-%ED%8E%B8">publisher/subscriber 작성하는 실습</a></li>
<li><a href="https://velog.io/@i_robo_u/%EB%82%B4-%EB%A1%9C%EB%B4%87%EC%9D%84-%ED%95%9C%EC%B8%B5-%EB%8D%94-%EB%98%91%EB%98%91%ED%95%98%EA%B2%8C-ROS2-Service-Server%EC%99%80-Client-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0">service server/client 작성하는 실습</a> </li>
</ul>
<hr>
<h1 id="실습">실습</h1>
<h2 id="실습-1-패키지-생성하기">실습 1. 패키지 생성하기</h2>
<p>일단 interface를 정의하기 위해선 새로운 패키지가 필요하다. </p>
<p>준비물 세션에 링크된 publisher/subscriber(줄여서 pub/sub) 패키지와 service/client 패키지를 사용할거기 때문에 같은 ros2_ws/src에 새로운 패키지 &quot;tutorial_interfaces&quot;를 만들자. </p>
<pre><code>cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 tutorial_interfaces</code></pre><p>msg와 srv를 만들기 위해서는 &quot;msg&quot;와 &quot;srv&quot;라는 디렉토리가 필요하다. 
이또한 만들어주자. </p>
<pre><code>cd ~/ros2_ws/src/tutorial_interfaces
mkdir msg srv</code></pre><p>(참고) mkdir msg srv 명령어는 각각 msg와 srv라는 디렉토리를 한번에 생성한다. </p>
<h2 id="실습-2-나만의-interface정의하기">실습 2. 나만의 interface정의하기</h2>
<h3 id="실습-21-msg-정의하기">실습 2.1. msg 정의하기</h3>
<p>이제 나만의 interface(msg와 srv등)을 만들면 된다. </p>
<p>먼저 msg경로로 이동해서 &quot;Num.msg&quot;라는 파일명을 갖는 파일을 만든다. </p>
<pre><code>cd ~/ros2_ws/src/tutorial_interfaces/msg
gedit Num.msg</code></pre><p>그다음 Num.msg에 int64 타입의 num이라는 변수명을 갖는 타입을 복사 붙여 넣기해 정의해준다. </p>
<pre><code>int64 num</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/6355d492-1255-455b-8f79-325096b6c3f4/image.png" alt=""></p>
<p>이 msg는 int64 type의 정보를 통신하는데 사용할 것이다. </p>
<p>추가로 Sphere.msg 파일도 생성해서 다음과 같이 center라는 변수와 radius변수를 정의한다. </p>
<pre><code>cd ~/ros2_ws/src/tutorial_interfaces/msg
gedit Sphere.msg</code></pre><pre><code>geometry_msgs/Point center
float64 radius</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/40077bc5-2792-49d5-a855-7301d4207fd0/image.png" alt=""></p>
<p>이 메세지도 나중에 원에 대한 정보를 통신하는데 사용할 예정이다. </p>
<h3 id="실습-22-srv-정의하기">실습 2.2. srv 정의하기</h3>
<p>다음으로는 srv파일들을 정의해보자. </p>
<p>srv 경로로 이동한 후 &quot;AddThreeInts.srv&quot;라는 파일을 만들자. </p>
<pre><code>cd ~/ros2_ws/src/tutorial_interfaces/srv
gedit AddThreeInts.srv</code></pre><p>그 후 파일 안은 다음과 같은 request와 response를 정의해준다. </p>
<pre><code>int64 a
int64 b
int64 c
---
int64 sum</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/2f92c36d-44e8-4f07-874b-a2b92bf3ce5f/image.png" alt=""></p>
<p>여기서 a,b,c는 request로 쓰일 int타입 정보이고, sum은 response에 해당 된다. </p>
<hr>
<h2 id="실습-3-cmakeliststxt-수정하기">실습 3. CMakeLists.txt 수정하기</h2>
<p>이렇게 정의한 msg와 srv를 header 파일 형식으로 만들어 c++ node code를 작성할 때 사용하려면 CMakeLists.txt에 다음과 같은 문구들을 추가해야한다. </p>
<p>먼저 CMakeLists.txt를 visual code로 열어준다. </p>
<pre><code>cd ~/ros2_ws/src/tutorial_interfaces
code .</code></pre><p>그 다음 밑에 내용을 추가한다. </p>
<pre><code>find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  &quot;msg/Num.msg&quot;
  &quot;msg/Sphere.msg&quot;
  &quot;srv/AddThreeInts.srv&quot;
  DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/145e82a3-8b12-4d9a-ae3b-b30d769a0c1b/image.png" alt=""></p>
<p>여기서 rosdil_generate_interfaces는 우리가 정의한 Num.msg, Sphere.msg, AddThreeInts.srv를 정의할 때 사용하는 CMake 함수이다.</p>
<p>참고로 rosdil은 ROS Interface Definition Language의 약자이다. </p>
<p>추가로 알아야 할 것은 msg와 srv를 정의할 때 사용한 int와 point 타입같은 것은 기존에 pre-defined된 타입이므로 int를 갖고 있는 msgs 패키지와 point를 갖고 있는 geometry 타입을 DEPENDENCIES에 추가해줘야한다. 
그리고 rosdil_generate_interfaces함수에 들어가는 첫번째 인자(argument)는 우리 패키지의 project 이름과 반드시 같아야 하는데 이는 &quot;${PROJECT NAME}&quot;이라고 넣어줌으로써 항상 같은 것을 보장하게 할 수 있다. </p>
<h2 id="실습-4-packagexml">실습 4. package.xml</h2>
<p>interface를 만들 때 pakcage.xml파일에 추가해줘야할 것이 4개정도 있다.</p>
<p>먼저 다음을 package.xml에 package format과 /packge라고 있는 공간 사이에 추가하자. 추가해야 하는 위치를 잘 모르겠다면 아래 예시 이미지를 참고하자! </p>
<pre><code>&lt;depend&gt;geometry_msgs&lt;/depend&gt;
&lt;buildtool_depend&gt;rosidl_default_generators&lt;/buildtool_depend&gt;
&lt;exec_depend&gt;rosidl_default_runtime&lt;/exec_depend&gt;
&lt;member_of_group&gt;rosidl_interface_packages&lt;/member_of_group&gt;</code></pre><p>(예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/2a96f10f-d612-4f53-ba50-dfbc3a48a73d/image.png" alt=""></p>
<p>각각을 살펴보면</p>
<ul>
<li>geometry_msgs: 우리 msg에 point와 같은 정보가 있기 때문에 dependency로 필요하다. </li>
<li>rosidl_default_generators: rosidl_default_generators라는 함수를 CMakeLists.txt에서 사용할 때 필요한 build tool dependency이다.</li>
<li>rosidl_default_runtime: interface를 만들게 되면 프로그램이 실행될 때 필요한 dependency가 있는데 rosidl_default_runtime이 이에 해당한다. </li>
<li>rosidl_interface_pakcages: ROS2에서는 interface를 만들게 되면 interface를 만든 해당 패키지(이번 실습의 경우 tutorial_interfaces 패키지)에 해당하는 그룹을 만들어 msg와 srv를 관리하게 된다. 이때 rosidl_interface_packages가 필요하다. </li>
</ul>
<p>뭐라 설명이 복잡한데, 이 요소들 특히 rosil~ 어쩌고 하는 애들은 msg와 srv가 만들 때 필요한 것이고, geometry_msgs 같은 애들은 우리가 정의하는 srv와 msg에 따라 달라진다고 생각하면된다. </p>
<p>사실 다 외우고 다니는 건 아니고 특정 interface를 정의하는게 필요한 경우 그때 그때 찾아보면 되니 이해 안된다고 걱정하지 말고 용어만 눈에 익혀두자. </p>
<h2 id="실습-5-tutorial_interfaces-패키지-build하기">실습 5. tutorial_interfaces 패키지 build하기</h2>
<p>자 그럼 tutorial_interfaces를 빌드할 차례다. </p>
<p>다음과 같이 build해주자. </p>
<pre><code>cd ~/ros2_ws
colcon build --packages-select tutorial_interfaces</code></pre><h2 id="실습-6-msg와-srv-생성-확인">실습 6. msg와 srv 생성 확인</h2>
<p>새로운 터미널을 열어 우리 작업환경을 source해주고 정상적으로 msg와 srv가 정상적으로 생성됐는지 확인하자. </p>
<p>이때 ros2 interface show라는 명령어를 이용하면 생성여부를 확인 할 수 있다. </p>
<pre><code>cd ~/ros2_ws
source install/setup.bash</code></pre><pre><code>ros2 interface show tutorial_interfaces/msg/Num</code></pre><p>그러면 다음과 같은 문구가 출력 될 것이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/5b1f7c31-644e-407b-b003-3cd7b01c00c9/image.png" alt=""></p>
<p>그 다음으로는 srv파일을 확인해보자. </p>
<pre><code>ros2 interface show tutorial_interfaces/srv/AddThreeInts</code></pre><p>(결과 예시)
<img src="https://velog.velcdn.com/images/i_robo_u/post/fbd883ee-45cd-44e2-b981-007c58f92213/image.png" alt=""></p>
<p>추가로 이 msg,srv에 해당하는 hpp파일은 다음과 같은 경로에 생성되어 있다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/76a14516-4660-43dc-8bb8-473a5caee66e/image.png" alt=""></p>
<h2 id="실습-7-만든-interface-테스트">실습 7. 만든 interface 테스트</h2>
<p>interface가 성공적으로 만들어진 것도 확인 했겠다, 이제 msg를 위한 publisher, subscriber 그리고 srv를 위한 service server와 client를 만들어 직접 만든 msg와 srv는 어떻게 사용하는지 알아보자. </p>
<h3 id="실습-71-pubsub을-만들어-nummsg-테스트하기">실습 7.1. pub/sub을 만들어 Num.msg 테스트하기</h3>
<p>** 주의: 이부분부터는 publsher/subscriber, service server/client 구현 실습을 진행 했어야 할 수 있는 부분이다.** </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/03f2c9d4-54b9-44cf-9700-712e0e086113/image.png" alt="">
위에 보이는 그림과 같이 지난시간에 만든 cpp_pubsub packge를 visual code로 열어준 다음 publisher_member_function.cpp와 subscriber_member_function.cpp를 하단의 코드들로 대체 해준다.(적절히 복사 붙여넣기) </p>
<ul>
<li>publisher_member_function.cpp 코드<pre><code>#include &lt;chrono&gt;
#include &lt;memory&gt;
</code></pre></li>
</ul>
<p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;tutorial_interfaces/msg/num.hpp&quot;                                            // CHANGE</p>
<p>using namespace std::chrono_literals;</p>
<p>class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node(&quot;minimal_publisher&quot;), count_(0)
  {
    publisher_ = this-&gt;create_publisher&lt;tutorial_interfaces::msg::Num&gt;(&quot;topic&quot;, 10);  // CHANGE
    timer_ = this-&gt;create_wall_timer(
      500ms, std::bind(&amp;MinimalPublisher::timer_callback, this));
  }</p>
<p>private:
  void timer_callback()
  {
    auto message = tutorial_interfaces::msg::Num();                                   // CHANGE
    message.num = this-&gt;count_++;                                                     // CHANGE
    RCLCPP_INFO_STREAM(this-&gt;get_logger(), &quot;Publishing: &#39;&quot; &lt;&lt; message.num &lt;&lt; &quot;&#39;&quot;);    // CHANGE
    publisher_-&gt;publish(message);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher&lt;tutorial_interfaces::msg::Num&gt;::SharedPtr publisher_;             // CHANGE
  size_t count_;
};</p>
<p>int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}</p>
<pre><code>달라진 점을 살펴보면 &quot;//CHANGE&quot;라고 써져 있는데 이전에 string type의 메세지를 publish하고 이용하는 부분들이 바뀐 것을 볼 수 있다. 


- subscriber_member_function.cpp 코드</code></pre><p>#include <functional>
#include <memory></p>
<p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;tutorial_interfaces/msg/num.hpp&quot;                                       // CHANGE</p>
<p>using std::placeholders::_1;</p>
<p>class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node(&quot;minimal_subscriber&quot;)
  {
    subscription_ = this-&gt;create_subscription&lt;tutorial_interfaces::msg::Num&gt;(    // CHANGE
      &quot;topic&quot;, 10, std::bind(&amp;MinimalSubscriber::topic_callback, this, _1));
  }</p>
<p>private:
  void topic_callback(const tutorial_interfaces::msg::Num &amp; msg) const  // CHANGE
  {
    RCLCPP_INFO_STREAM(this-&gt;get_logger(), &quot;I heard: &#39;&quot; &lt;&lt; msg.num &lt;&lt; &quot;&#39;&quot;);     // CHANGE
  }
  rclcpp::Subscription&lt;tutorial_interfaces::msg::Num&gt;::SharedPtr subscription_;  // CHANGE
};</p>
<p>int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}</p>
<pre><code>subscriber코드에서도 비슷하게 변화된 부분이 CHANGE라고 표시되어 잇는데 &quot;std_msgs/msg/string.hpp&quot;에서 정의된 type들이 &quot;tutorial_interfaces/msg/num.hpp&quot;로 새롭게 정의한 num type을 사용하여 subscribe하고 데이터 처리는 하고 있다. 


이제 CMakeLists.txt를 작성할 차례다. 

다음과 같이 CMakeLists.txt에 복사 붙여넣기 하자. 어디다 복사 붙여넣기 해야할 지 모르는 사람은 아래 예시 이미지를 참고하자. </code></pre><p>find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)                      # CHANGE</p>
<p>add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces)    # CHANGE</p>
<p>add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces)  # CHANGE</p>
<p>install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})</p>
<p>ament_package()</p>
<pre><code>
- 바꾸기 전 CMakeLists.txt
![](https://velog.velcdn.com/images/i_robo_u/post/a8490d16-25e8-4696-8bea-c0b16d94f6b7/image.png)

- 바뀐 후 CMakeLists.txt
![](https://velog.velcdn.com/images/i_robo_u/post/10ffedb9-9fd2-49cd-8361-1207408ca2e7/image.png)

바뀐 부분을 살펴보면 std_msgs 패키지에서 tutorial_interfaces로 변한 것을 확인할 수 있다. 

마지막으로 pakcage.xml에 tutorial_interfaces dependency도 추가해주자. </code></pre><p><depend>tutorial_interfaces</depend></p>
<pre><code>
![](https://velog.velcdn.com/images/i_robo_u/post/eb252788-b0f3-4d29-ab45-35534d51bd9d/image.png)

그럼 colcon build하면된다! 참고로 파일을 변경했으면 꼭 ctrl+
S를 눌러 저장하자!
</code></pre><p>cd ~/ros2_ws
colcon build --packages-select cpp_pubsub</p>
<pre><code>
build가 완료 됐으면 새로운 terminal을 열어서 source해주자. </code></pre><p>cd ~/ros2_ws
source install/setup.bash</p>
<pre><code>
그 후 publisher를 ros2 run으로 실행한다. </code></pre><p>ros2 run cpp_pubsub talker</p>
<pre><code>
새 터미널을 열어서 비슷하게 listener도 실행해주자.</code></pre><p>cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_pubsub listener</p>
<pre><code>
그러며 하단 이미지와 같이 msg를 publish하고 subscribe하는 log를 볼수 있다. 
![](https://velog.velcdn.com/images/i_robo_u/post/9e088bb3-7649-482f-8585-0c04c450b45f/image.png)


### 실습 7.2. &quot;AddThreeInts.srv&quot; 테스트하기
저번 시간에는 2개의 숫자를 더하는 코드를 짰었다. 

이번에 만든 srv 파일 이름에서 유추할 수 있듯이 이번에는 3개의 숫자를 더하는 service server와 이를 호출하는 client 코드를 작성해볼 것이다. 

먼저 pub/sub terminal에가서 Ctrl+C를 눌러 프로그램들을 종료해주자. 

그 다음 ~/ros2_ws/src/cpp_srvcli 경로로 이동해서 visual code로 변형할 코드들을 열어주자. </code></pre><p>cd ~/ros2_ws/src/cpp_srvcli
code .</p>
<pre><code>
(예시)
![](https://velog.velcdn.com/images/i_robo_u/post/41eac341-f8eb-4830-bf75-6e111de677b2/image.png)

그 다음 add_two_ints_server.cpp를 변경해준다.

- add_two_ints_server.cpp 코드</code></pre><p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;tutorial_interfaces/srv/add_three_ints.hpp&quot;                                        // CHANGE</p>
<p>#include <memory></p>
<p>void add(const std::shared_ptr&lt;tutorial_interfaces::srv::AddThreeInts::Request&gt; request,     // CHANGE
          std::shared_ptr&lt;tutorial_interfaces::srv::AddThreeInts::Response&gt;       response)  // CHANGE
{
  response-&gt;sum = request-&gt;a + request-&gt;b + request-&gt;c;                                      // CHANGE
  RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Incoming request\na: %ld&quot; &quot; b: %ld&quot; &quot; c: %ld&quot;,  // CHANGE
                request-&gt;a, request-&gt;b, request-&gt;c);                                         // CHANGE
  RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;sending back response: [%ld]&quot;, (long int)response-&gt;sum);
}</p>
<p>int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);</p>
<p>  std::shared_ptr<a href="rclcpp::Node">rclcpp::Node</a> node = rclcpp::Node::make_shared(&quot;add_three_ints_server&quot;);   // CHANGE</p>
<p>  rclcpp::Service&lt;tutorial_interfaces::srv::AddThreeInts&gt;::SharedPtr service =               // CHANGE
    node-&gt;create_service&lt;tutorial_interfaces::srv::AddThreeInts&gt;(&quot;add_three_ints&quot;,  &amp;add);   // CHANGE</p>
<p>  RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Ready to add three ints.&quot;);                     // CHANGE</p>
<p>  rclcpp::spin(node);
  rclcpp::shutdown();
}</p>
<pre><code>
바뀐 부분은 CHANGE로 표시되어있는데 주로 example_interfaces/srv/add_two_ints.hpp 에서 add_two_ints가 사용되던 부분이 tutorial_interfaces/srv/add_three_ints.hpp의 add_three_ints가 사용되게끔 변경되었다. service 이름도 숫자 3개를 더하는 의미를 담도록 바뀌었다. 


비슷하게 add_two_ints_client도 변형해준다. 

- add_two_ints_client 코드</code></pre><p>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;tutorial_interfaces/srv/add_three_ints.hpp&quot;                                       // CHANGE</p>
<p>#include <chrono>
#include <cstdlib>
#include <memory></p>
<p>using namespace std::chrono_literals;</p>
<p>int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);</p>
<p>  if (argc != 4) { // CHANGE
      RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;usage: add_three_ints_client X Y Z&quot;);      // CHANGE
      return 1;
  }</p>
<p>  std::shared_ptr<a href="rclcpp::Node">rclcpp::Node</a> node = rclcpp::Node::make_shared(&quot;add_three_ints_client&quot;);  // CHANGE
  rclcpp::Client&lt;tutorial_interfaces::srv::AddThreeInts&gt;::SharedPtr client =                // CHANGE
    node-&gt;create_client&lt;tutorial_interfaces::srv::AddThreeInts&gt;(&quot;add_three_ints&quot;);          // CHANGE</p>
<p>  auto request = std::make_shared&lt;tutorial_interfaces::srv::AddThreeInts::Request&gt;();       // CHANGE
  request-&gt;a = atoll(argv[1]);
  request-&gt;b = atoll(argv[2]);
  request-&gt;c = atoll(argv[3]);                                                              // CHANGE</p>
<p>  while (!client-&gt;wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Interrupted while waiting for the service. Exiting.&quot;);
      return 0;
    }
    RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;service not available, waiting again...&quot;);
  }</p>
<p>  auto result = client-&gt;async_send_request(request);
  // Wait for the result.
  if (rclcpp::spin_until_future_complete(node, result) ==
    rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Sum: %ld&quot;, result.get()-&gt;sum);
  } else {
    RCLCPP_ERROR(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Failed to call service add_three_ints&quot;);    // CHANGE
  }</p>
<p>  rclcpp::shutdown();
  return 0;
}</p>
<pre><code>


server와 비슷하게 바뀐 부분은 CHANGE로 표시되어있는데 주로 example_interfaces/srv/add_two_ints.hpp 에서 add_two_ints가 사용되던 부분이 tutorial_interfaces/srv/add_three_ints.hpp의 add_three_ints가 사용되게끔 변경되었다. service 이름도 숫자 3개를 더하는 의미를 담도록 바뀌었다.


그 다음 CMakeLists.txt를 변형해주자. </code></pre><p>#...</p>
<p>find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)         # CHANGE</p>
<p>add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
  rclcpp tutorial_interfaces)                      # CHANGE</p>
<p>add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
  rclcpp tutorial_interfaces)                      # CHANGE</p>
<p>install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME})</p>
<p>ament_package()</p>
<pre><code>
tutorial_interfaces 패키를 사용하도록 변경했으며 특이한점은 .cpp파일 자체 이름은 편의상 add_two_ints~.cpp로 유지하고 있다. 

(예시)
![](https://velog.velcdn.com/images/i_robo_u/post/3d694785-76ad-43bb-8a7e-b2b4fd29e65d/image.png)

마지막으로 package.xml에서 tutorial_interfaces를 dependency로 추가해준다.
</code></pre><p><depend>tutorial_interfaces</depend></p>
<pre><code>(예시)
![](https://velog.velcdn.com/images/i_robo_u/post/2aab1295-ce24-49fb-9aa5-3ee4072c3eba/image.png)


이제 colcon build할 차례다! build하기 전에 변경한 파일들을 다 제대로 저장했는지 Ctrl+S를 눌러서 한 번 더 확인한다. </code></pre><p>cd ~/ros2_ws
colcon build --packages-select cpp_srvcli</p>
<pre><code>
새로운 터미널을 열어서 source하고 service server를 실행해준다.</code></pre><p>cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_srvcli server</p>
<pre><code>
비슷하게 새로운 터미널을 열어서 source하고 service client를 실행해준다. 단, 이때 더할 인자 3개를 추가로 입력해준다. </code></pre><p>cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_srvcli client 2 3 1</p>
<pre><code>
(결과 예시)
![](https://velog.velcdn.com/images/i_robo_u/post/60ae7d64-9afd-4dad-9e93-ea6d9eb4382b/image.png)

그러면 이렇게 2,3,1을 더한 6이 반환되는 것을 볼 수 있다. 

---
자 오늘은 이렇게 msg와 srv를 직접 만들고 pub/sub과 server/client 코드를 직접 작성해서 어떻게 사용하는지 또한 알아봤다. 

만드는 과정을 요약하자면 다음과 같다. 

#### 만드는 과정 요약
- interface를 정의할 package를 만든다. 
- srv와 msg 경로를 만든다. 
- .msg와 .srv 이름을 갖는 파일을 만들고 알맞게 원하는 타입들을 정의한다. 
- CMakeLists.txt와 package.xml에 interface를 만들 때 필요한 함수와 요소들을 추가한다. msg와 srv에 dependency가 있다면 그 것도 추가한다.
- colcon build한다. 
- 새로운 msg와 srv의 hpp파일은 install 경로에 저장 되어 있으며 이 hpp파일들은 나중에 cpp 코딩할 때 사용된다. 

#### 사용하는 과정 요약
- include를 통해 사용하고자하는 msg 및 srv의 hpp파일을 포함시킨다. 
- pub/sub이나 server/client에 맞게 타입들을 정해주고 사용한다. 


어려워 보이지만 내 msg 또는 srv를 만들어야 할 때가 오면 어떻게 만드는지 그 순서를 보고 참고해서 하면되니 너무 외울려고 하진말고 그 흐름을 이해하도록 노력하자. 

다음 시간에는 ros2 run을 대체할 수 있는 launch 파일 만드는 법에 대해 배워볼 예정이다. 

---
오늘도 여기까지 온 스스로를 칭찬해주자!

질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!


문의메일: irobou0915@gmail.com

오픈톡 문의: https://open.kakao.com/o/sXMqcQAf

[IRoboU 유튜브 채널](https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg)

[참고 문헌](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.html)

![](https://velog.velcdn.com/images/i_robo_u/post/41e0cf18-2c17-4738-b191-56a870a52cf7/image.jpg)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[내 로봇을 한층 더 똑똑하게 ROS2 Service Server와 Client 작성하기]]></title>
            <link>https://velog.io/@i_robo_u/%EB%82%B4-%EB%A1%9C%EB%B4%87%EC%9D%84-%ED%95%9C%EC%B8%B5-%EB%8D%94-%EB%98%91%EB%98%91%ED%95%98%EA%B2%8C-ROS2-Service-Server%EC%99%80-Client-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@i_robo_u/%EB%82%B4-%EB%A1%9C%EB%B4%87%EC%9D%84-%ED%95%9C%EC%B8%B5-%EB%8D%94-%EB%98%91%EB%98%91%ED%95%98%EA%B2%8C-ROS2-Service-Server%EC%99%80-Client-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 28 Jan 2024 13:01:07 GMT</pubDate>
            <description><![CDATA[<p>로봇을 개발 할 때 publisher와 subscriber만으로는 똑똑한 로봇을 만드는데 상당한 제약을 받는다. </p>
<p>이번에는 이를 극복하는 것을 도와줄 service server와 client작성법에 대해 알아보자. </p>
<hr>
<p>&lt;목표&gt;
C++로 service server와 client를 작성하고 실행해본다. </p>
<p><strong>tutorial level</strong>: 입문</p>
<p><strong>예상 소요시간</strong>
약 20분</p>
<p><strong>영상 튜토리얼</strong>
<a href="https://youtu.be/wvTRyd0LxY8?feature=shared">튜토리얼 링크</a></p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>service는 두가지 요소로 이루어져있다. </p>
<ol>
<li>service를 제공하는 server</li>
<li>service를 요청하는 client</li>
</ol>
<p>사람이 소통할 때 같은 언어를 사용해야 하듯이 service도 요청하고 응답하려면 특정한 형식을 지켜야한다. 이런 규칙을 정의하는게 .srv 파일이다. </p>
<p>이번 시간에는 두 숫자를 더하는 서비스를 제공하는 server를 만들고 이를 요청하는 client를 구현 해볼 것이다. </p>
<hr>
<h1 id="준비물">준비물</h1>
<p>지난 시간에 배웠던 <a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Service-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-4">service 개념</a>, <a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0">작업공간 만들기 실습</a>과 <a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0">패키지 생성하기 실습</a>을 했으면 오늘 준비물은 다 갖춰졌다. </p>
<hr>
<h1 id="실습">실습</h1>
<h2 id="실습-1-패키지-만들기">실습 1. 패키지 만들기</h2>
<p>지난 시간에 패키지를 만드는 명령어 ros2 pkg create를 사용해 cpp_srvcli라는 이름의 패키지를 생성해준다. 
이 패키지는 dependency로 rclcpp, example_interfaces를 갖는데 여기서 example_interfaces는 오늘 우리가 만들 service의 형식 .srv 파일을 제공한다고 보면된다. </p>
<pre><code>cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_srvcli --dependencies rclcpp example_interfaces</code></pre><p>--dependencies라는 tag로 패키지의 dependency를 사전에 추가해줬기 때문에 CMakeLists.txt나 package.xml을 수정하는 노고가 조금 줄어 들었다. </p>
<p>추가로 example_interfaces 패키지가 제공하는 .srv 파일의 구조는 다음과 같다. </p>
<pre><code>int64 a
int64 b
---
int64 sum</code></pre><p>간단히 위의 구조를 살펴보면 위의 int64 a,b는 service의 request형식에 해당하고 &quot;---&quot; 밑의 int 64 sum은 서비스가 기능을 수행하고 반환하는 response(또는 result)에 해당한다. </p>
<h3 id="실습-11-packagexml-업데이트-하기">실습 1.1 package.xml 업데이트 하기</h3>
<p>dependency는 이미 추가 되어있으나 배포할 때 수정해줘야할 description, maintainer는 아직 변경되지 않았다. package.xml을 열어 적절히 수정해주자. </p>
<h2 id="실습-2-service-server-code-작성하기">실습 2. service server code 작성하기</h2>
<p>이전에 만든 cpp_srvcli/src 경로에 가서 add_two_ints_server.cpp 파일을 만든다. </p>
<pre><code>cd ~/ros2_ws/cpp_srvcli/src
gedit add_two_ints_server.cpp</code></pre><p>그 다음 하단의 코드를 복사 붙여넣기 해준다. </p>
<pre><code>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;example_interfaces/srv/add_two_ints.hpp&quot;

#include &lt;memory&gt;

void add(const std::shared_ptr&lt;example_interfaces::srv::AddTwoInts::Request&gt; request,
          std::shared_ptr&lt;example_interfaces::srv::AddTwoInts::Response&gt;      response)
{
  response-&gt;sum = request-&gt;a + request-&gt;b;
  RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Incoming request\na: %ld&quot; &quot; b: %ld&quot;,
                request-&gt;a, request-&gt;b);
  RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;sending back response: [%ld]&quot;, (long int)response-&gt;sum);
}

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

  std::shared_ptr&lt;rclcpp::Node&gt; node = rclcpp::Node::make_shared(&quot;add_two_ints_server&quot;);

  rclcpp::Service&lt;example_interfaces::srv::AddTwoInts&gt;::SharedPtr service =
    node-&gt;create_service&lt;example_interfaces::srv::AddTwoInts&gt;(&quot;add_two_ints&quot;, &amp;add);

  RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Ready to add two ints.&quot;);

  rclcpp::spin(node);
  rclcpp::shutdown();
}</code></pre><h3 id="실습-21-코드-분석">실습 2.1 코드 분석</h3>
<p>제일 처음 두 줄은 rclcpp와 example_interfaces와 같은 dependency를 추가 하고 있는 구문이다. </p>
<p>그다음 memory는 shared_ptr를 사용하기 위해 추가해준다. </p>
<p>add라는 함수를 정의하는데 함수 인자로는 request와 response를 갖는다. 
request의 a와 b는 client가 호출할 때 제공할 것이고 이 둘을 합친 값을 response-&gt;sum에 저장한다. </p>
<p>그 후 결과를 log를 통해 출력하는 것으로 함수는 마무리 된다. </p>
<p>이 함수를 service server에 등록해줘야 사용가능한데 그 부분은 main 함수에 구현되어 있다. </p>
<p>먼저 node를 만들기 위해 init을 해주고 rclcpp::Node를 share_ptr로 만들어서 &quot;add_two_ints_server&quot;라는 이름으로 node를 만들어준다. </p>
<p>그 후 example_interfaces::srv::AddTwoInts의 srv 타입을 사용하는 service를 선언하고 해당 service가 &quot;add_two_ints&quot;라는 이름을 갖도록 서비스를 생성해준다. 해당 서비스가 할 기능은 앞서 add에 구현했었는데 이를 &amp;add를 통해 service로 등록해준다. </p>
<p>그 후 두 숫자를 더할 서비스가 준비됐다는 메세지와 함께 rclcpp::spin(node)를 통해 node에 등록된 서비스 서버를 활성화 시키고 대기하도록 한다. 
중간에 사용자가 ctrl+C같이 중지 명령을 내리면 node를 shutdown하고 프로그램을 종료시킨다. </p>
<p>이처럼 service server을 구현할 때는 다음과 같은 구조를 따라 만들면 된다. </p>
<ol>
<li>server에서 동작할 함수 정의하기</li>
<li>server를 node에 등록하고 기능을 담당하는 함수도 등록해주기</li>
</ol>
<h3 id="실습-22-executable-추가하기">실습 2.2 executable 추가하기</h3>
<p>C++로 구현하게 되면 실행을 위해 executable을 만드는 구문을 CMakeLists.txt에 추가해야한다. </p>
<p>다음과 같은 구문을 CMakeLists.txt에 추가한다. 
executable 이름은 sever이고 dependency로는 rclcpp와 example_interfaces를 갖는 다는 것을 말해주고 있다. </p>
<pre><code>add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server rclcpp example_interfaces)</code></pre><p>그 다음 이 executable server을 설치할 장소를 다음과 같이 명시해준다. </p>
<pre><code>install(TARGETS
    server
  DESTINATION lib/${PROJECT_NAME})</code></pre><p>이 것으로 service server 구현을 위한 작업은 완료되어 colcon build가 가능하지만 service client 또한 구현해서 한 번에 구현하도록 하자. </p>
<hr>
<h2 id="실습-3-client-node-구현하기">실습 3. client node 구현하기</h2>
<p>이전과 비슷하게 ros2_ws/src/cpp_srvcli/src 경로에 add_two_ints_client.cpp라는 파일을 만든다. </p>
<pre><code>cd ~/ros2_ws/src/cpp_srvcli/src
gedit add_two_ints_client.cpp</code></pre><p>그 다음 하단의 코드를 복사 붙여넣기 한다. </p>
<pre><code>#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;example_interfaces/srv/add_two_ints.hpp&quot;

#include &lt;chrono&gt;
#include &lt;cstdlib&gt;
#include &lt;memory&gt;

using namespace std::chrono_literals;

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

  if (argc != 3) {
      RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;usage: add_two_ints_client X Y&quot;);
      return 1;
  }

  std::shared_ptr&lt;rclcpp::Node&gt; node = rclcpp::Node::make_shared(&quot;add_two_ints_client&quot;);
  rclcpp::Client&lt;example_interfaces::srv::AddTwoInts&gt;::SharedPtr client =
    node-&gt;create_client&lt;example_interfaces::srv::AddTwoInts&gt;(&quot;add_two_ints&quot;);

  auto request = std::make_shared&lt;example_interfaces::srv::AddTwoInts::Request&gt;();
  request-&gt;a = atoll(argv[1]);
  request-&gt;b = atoll(argv[2]);

  while (!client-&gt;wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Interrupted while waiting for the service. Exiting.&quot;);
      return 0;
    }
    RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;service not available, waiting again...&quot;);
  }

  auto result = client-&gt;async_send_request(request);
  // Wait for the result.
  if (rclcpp::spin_until_future_complete(node, result) ==
    rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Sum: %ld&quot;, result.get()-&gt;sum);
  } else {
    RCLCPP_ERROR(rclcpp::get_logger(&quot;rclcpp&quot;), &quot;Failed to call service add_two_ints&quot;);
  }

  rclcpp::shutdown();
  return 0;
}</code></pre><h3 id="실습-31-코드-분석">실습 3.1. 코드 분석</h3>
<p>먼저 rclcpp와 example_interfaces가 dependency이므로 추가한다. </p>
<p>시간에 대한 변수나 함수를 사용하기 위해 chrono도 추가해준다. 
memory는 shared_ptr를 사용하기 위함이고 cstdlib같은 경우는 추후에 코드가 나오면 설명하도록 하겠다. </p>
<p>std::chrono_literals를 namespace로 사용함으로써 1s(1초)와 같이 사람이 읽기 쉬운 변수를 사용할 수 있다. </p>
<p>main 함수에서는 먼저 입력된 인자가 3개인지 확인하고 3개가 아닐 시 잘 못된 사용임을 알려주고 프로그램을 종료한다. </p>
<p>만약 인자 개수가 정상적으로 3개 입력됐다면, &quot;add_two_ints_client&quot;이름으로 node를 만들고 그 node에 어떤 service를 사용할지와 그 srv 타입은 어떤건지 명시해서 client를 만들어준다. </p>
<p>그 후 서비스를 호출하기 위해 request를 정의하는데 이때 request의 a와 b값은 사용자가 입력한 인자 1,2번에서 획득한다. 주목할 점은 atoll이란 함수 인데 처음에 추가해준 cstdlib에서 제공하는 함수로써 입력된 문자열을 long long int로 변환해준다. </p>
<p>서비스를 호출하기전 안전장치로 서비스 서버가 존재하는지 확인한다. 이는 client-&gt;wait_for_service를 1초마다 호출하는 while문을 작성해서 구현 할 수 있다. 
이렇게 service를 기다리는 도중 ctrl+C같은 중지 명령이 들어오면 &quot;Interrupted while waiting for the service. Exiting.&quot;라는 에러메세지를 띄우고, 중지 없이 service가 발견되지 않으면 기다리는 중이라고 메세지를 띄운다. </p>
<p>정상적으로 service가 발견되면 client는 asyn_send_request를 통해 request를 보내게 된다. 
이때 반환 되는 result는 rclcpp:spin_until_future_complete에 node와 함께 인자로 들어감으로써 완료 될 때까지 확인하는 함수를 사용할 수 있게 한다. </p>
<p>만약 이 함수에 반환 값이 SUCCESS이면 해당 서비스는 성공적으로 호출 됐다는 의미 이므로 result에 저장되어있는 sum값을 출력한다. </p>
<p>SUCCESS가 아닌경우에는 호출 실패라는 메세지를 출력한다. </p>
<p>그다음 node를 안전히 종료하고 프로그램을 마친다. </p>
<p>이렇게 client를 구현할 때 다음 단계를 따르면 쉽게 구현할 수 있다. </p>
<ul>
<li>node에 client가 사용할 srv 타입과 함께 등록한다. </li>
<li>요청할 request를 만든다. </li>
<li>호출하기 전에 service가 존재하는지 유무를 while문 등으로 확인한다. </li>
<li>존재하면 request를 보내 result(response)를 획득한다. </li>
<li>response를 성공 유무에 따라 적절히 대응하는 코드를 작성한다. </li>
</ul>
<hr>
<h3 id="실습-32-executable-추가">실습 3.2. executable 추가</h3>
<p>server와 마찬가지고 client도 executable을 추가해준다. </p>
<p>완성된 CMakeLists.txt는 다음과 같다. </p>
<pre><code>cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server rclcpp example_interfaces)

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client rclcpp example_interfaces)

install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME})

ament_package()</code></pre><hr>
<h2 id="실습-4-빌드-및-실행">실습 4. 빌드 및 실행</h2>
<p>모든 재료가 완성됐으므로 이제 빌드하고 실행하면 된다. </p>
<p>그전에 dependency를 rosdep으로 설치해준다. </p>
<pre><code>cd ~/ros2_ws
rosdep install -i --from-path src --rosdistro humble -y</code></pre><p>그 후 colcon build로 원하는 패키지 cpp_srvcli를 빌드한다. </p>
<pre><code>colcon build --packages-select cpp_srvcli</code></pre><p>빌드한 터미널과 다른 터미널을 열어 작업환경을 소스하고 server를 실행시킨다. </p>
<pre><code>cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_srvcli server</code></pre><p>그럼 다음과 같이 준비됐다는 메세지가 뜰 것이다. </p>
<pre><code>[INFO] [rclcpp]: Ready to add two ints.</code></pre><p>또다른 터미널을 열어서 client를 2개의 int 인자와 함께 실행해준다. </p>
<pre><code>cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_srvcli client 2 3</code></pre><p>그러면 Sum 결과 5라는 숫자가 출력될 것이다. </p>
<pre><code>[INFO] [rclcpp]: Sum: 5</code></pre><p>다시 server를 실행했던 터미널에 가면 다음과 같이 처리 과정이 출력되어 있을 것이다. </p>
<pre><code>[INFO] [rclcpp]: Incoming request
a: 2 b: 3
[INFO] [rclcpp]: sending back response: [5]</code></pre><hr>
<p>이렇게 오늘은 우리 로봇이 똑똑한 요청 응답 기능을 할 수 있도록 도와주는 service server와 client를 직접 구현하는 방법을 알아봤다. </p>
<p>앞으로 service server와 client를 만드는 과정을 간단히 요약하면 다음과 같다. </p>
<p><strong>&quot;service server 만들기&quot;</strong></p>
<ol>
<li>server에서 동작할 함수 정의하기</li>
<li>server를 node에 등록하고 기능을 담당하는 함수도 등록해주기</li>
</ol>
<p><strong>&quot;service client 만들기&quot;</strong></p>
<ol>
<li>node에 client가 사용할 srv 타입과 함께 등록한다. </li>
<li>요청할 request를 만든다. </li>
<li>호출하기 전에 service가 존재하는지 유무를 while문 등으로 확인한다. </li>
<li>존재하면 request를 보내 result(response)를 획득한다. </li>
<li>response를 성공 유무에 따라 적절히 대응하는 코드를 작성한다.</li>
</ol>
<hr>
<p>오늘도 여기까지 온 스스로를 격하게 칭찬해주자!</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a></p>
<p>오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a></p>
<p>IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">링크텍스트</a></p>
<p>참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Service-And-Client.html">링크텍스트</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/e6e9ee4a-a5b2-4816-9128-d7b5120613a3/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ROS2의 꽃 publisher와 subscriber(C++ 편)]]></title>
            <link>https://velog.io/@i_robo_u/ROS2%EC%9D%98-%EA%BD%83-publisher%EC%99%80-subscriberC-%ED%8E%B8</link>
            <guid>https://velog.io/@i_robo_u/ROS2%EC%9D%98-%EA%BD%83-publisher%EC%99%80-subscriberC-%ED%8E%B8</guid>
            <pubDate>Sun, 21 Jan 2024 09:59:32 GMT</pubDate>
            <description><![CDATA[<p>여태까지 명령어랑 개념을 배웠다면 이제는 본격적으로 로봇개발에 기능을 만들 수 있게 해주는 publisher와 subscriber를 작성하는 예시를 이번 시간에 살펴볼 것이다. </p>
<hr>
<p>목표:
C++로 publisher와 subscriber node를 작성하고 실행한다. </p>
<p>tutorial level: 입문</p>
<p>예상 소요시간
약 20분</p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>예전에 topic 개념을 배웠듯이 ROS2는 node라는 곳에서 메세지를 publish(발행)하고 또다른 node에서 그 메세지를 subscribe(구독)하는 식으로 통신이 일어난다. </p>
<p>링크: <a href="https://velog.io/@i_robo_u/ROS2-Humble-Node-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-2">node란?</a>
링크: <a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Topic-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-3">topic이란?</a></p>
<p>예를 들어 &quot;talker&quot;라는 node는 어떤 숫자의 메세지를 주기적으로 publish했고, &quot;listener&quot;라는 node는 그 숫자들을 주기적으로 subscribe했었다. </p>
<h1 id="준비물">준비물</h1>
<p>다음 포스트에 있는 실습을 마친 사람들이 이번 실습을 원활히 진행할 수 있다. </p>
<ul>
<li><a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0">workspace만들기</a></li>
<li><a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0">package 만들기</a></li>
</ul>
<h1 id="실습">실습</h1>
<h2 id="실습1-패키지-만들기">실습1. 패키지 만들기</h2>
<p>ros2라는 command를 사용하기 위해 ros2 workspace를 먼저 source해주자. </p>
<pre><code>source /opt/ros/humble/setup.bash</code></pre><p>지난 시간에 만들었던 ros2_ws/src directory로 이동해서 ros2 pkg create를 이용해서 패키지를 만들어주자. 
CMake로 패키지를 만들 것이고, license로는 Apach-2.0, cpp_pubsub이라는 패키지 이름을 붙여줄 것이다. </p>
<pre><code>cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub</code></pre><p>패키지를 생성했으면 패키지 안에 있는 src 폴더로 이동하자. </p>
<pre><code>cd ~/ros2_ws/src/cpp_pubsub/src</code></pre><h2 id="실습2-publisher-node작성하기">실습2. publisher node작성하기</h2>
<p>쉽게 설명하기 위해 미리 작성된 코드를 다운 받고 코드를 살펴보는 식으로 진행하겠다. </p>
<p>먼저 ros2_ws/src/cpp_pubsub/src에서 다음과 같이 코드를 다운 받아주자. </p>
<pre><code>wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_publisher/member_function.cpp</code></pre><p>그럼 다음과 같이 publisher_member_function.cpp라는 파일이 설치 된 것을 볼 수 있을 것이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/347bf246-8d89-4e2f-86cb-4ccbb89d76f1/image.png" alt=""></p>
<h3 id="visual-code-설치하기코드-에디터">visual code 설치하기(코드 에디터)</h3>
<ul>
<li><p>&quot;ubuntu software&quot;이모티콘을 누른다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/11f1f102-c037-4229-b30f-86cfb68cd91a/image.png" alt=""></p>
</li>
<li><p>code를 검색한다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/f95709a7-c5d0-4a78-85c2-8c6a9cc746c5/image.png" alt=""></p>
</li>
</ul>
<p>-visual code를 찾아 클릭한 후 install을 눌러준다.
<img src="https://velog.velcdn.com/images/i_robo_u/post/903904b0-003a-4865-ae57-04f3412ca538/image.png" alt=""></p>
<p>-비밀번호를 입력하고 기다린다.
<img src="https://velog.velcdn.com/images/i_robo_u/post/42f57f39-c7d9-4d8a-bc2d-fe6519cecaed/image.png" alt="">
<img src="https://velog.velcdn.com/images/i_robo_u/post/ea6a3c32-0484-464a-9f4c-fa9674da3959/image.png" alt=""></p>
<p><a href="https://junorionblog.co.kr/ubuntu22-04%EC%97%90-visual-studio-code-%EC%84%A4%EC%B9%98/">(다른 방법)커맨드 라인으로 설치하기</a></p>
<h2 id="실습2-이어서">실습2 이어서.</h2>
<p>publsher_member_function.cpp를 다운 받았으면 방금 설치한 visual code로 열어준다. </p>
<pre><code>cd ~/ros2_ws/src/cpp_pubsub/src
code . #현재 경로에 있는 파일 및 폴더를 visual code로 연다. </code></pre><p>예시
<img src="https://velog.velcdn.com/images/i_robo_u/post/3718b75c-f1c8-4693-b7c4-6ae30f85197c/image.png" alt=""></p>
<p>그러면 다음과 같은 코드가 뜰 것이다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/74a09089-8da8-4b20-9338-e21cc9ffda15/image.png" alt=""></p>
<p>&lt;text 버전&gt;</p>
<pre><code>#include &lt;chrono&gt;
#include &lt;functional&gt;
#include &lt;memory&gt;
#include &lt;string&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node(&quot;minimal_publisher&quot;), count_(0)
    {
      publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(&quot;topic&quot;, 10);
      timer_ = this-&gt;create_wall_timer(
      500ms, std::bind(&amp;MinimalPublisher::timer_callback, this));
    }

  private:
    void timer_callback()
    {
      auto message = std_msgs::msg::String();
      message.data = &quot;Hello, world! &quot; + std::to_string(count_++);
      RCLCPP_INFO(this-&gt;get_logger(), &quot;Publishing: &#39;%s&#39;&quot;, message.data.c_str());
      publisher_-&gt;publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher&lt;std_msgs::msg::String&gt;::SharedPtr publisher_;
    size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared&lt;MinimalPublisher&gt;());
  rclcpp::shutdown();
  return 0;
}</code></pre><h3 id="실습-21-코드-설명">실습 2.1 코드 설명</h3>
<p>예제 코드를 하나씩 살펴보자. </p>
<pre><code>#include &lt;chrono&gt;
#include &lt;functional&gt;
#include &lt;memory&gt;
#include &lt;string&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;

using namespace std::chrono_literals;</code></pre><p>먼저 C++ standard library들인 chrono, functional, memory, string을 include하고 있다. 
그다음에 rclcpp/rclcpp.hpp를 include하게 되는데 이는 ROS2를 cpp로 개발할 때 가장 흔하게 필요한 헤더파일이다. </p>
<p>그 밑에 std_msgs/msg/string.hpp 는 ROS2 standard message중 string에 해당하는 message type을 사용하기 위한 헤더파일로써 publish할 때 string data type을 사용할 것이기 때문에 필요하다. </p>
<p>다음 using namcespace~는 std::chrono_literals를 매번 입력하는 수고를 덜기위해 작성해준다. </p>
<pre><code>class MinimalPublisher : public rclcpp::Node</code></pre><p>다음 살펴볼 라인은 rclcpp::Node에서 상속받아 MinimalPublisher라는 node class를 만들고 있다. 
이 코드 밑에 사용되는 this라는 키워드는 이 node를 가리키게 된다. </p>
<pre><code>public:
  MinimalPublisher()
  : Node(&quot;minimal_publisher&quot;), count_(0)
  {
    publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(&quot;topic&quot;, 10);
    timer_ = this-&gt;create_wall_timer(
    500ms, std::bind(&amp;MinimalPublisher::timer_callback, this));
  }</code></pre><p>다음 코드에서는 public constructor, MinimalPublisher를 정의하는데
이 constructor는 node이름을 &quot;minimal_publisher&quot;로 정하고, count_를 0으로 초기화 한다. 
그 후 &quot;publisher_&quot;를 string 타입의 메세지를 사용하도록 선언한다. 
&quot;topic&quot;은 이 publihser가 메세지를 발행할 topic의 이름이고, topic이 가지고 있을 수 있는 message의 한계 개수는 10개로 지정한다. </p>
<p>다음 timer_라는 변수에 &quot;timer_callback&quot;함수를 선언한다. timer 함수는 보통 주기적으로 어떤 함수를 실행하고 싶을 때 사용하는데 그 주기는 지금 500ms(1초에 2번)으로 설정되어있으며, timer_callback함수에 정의된 기능을 실행하게 될 것이다. </p>
<pre><code>private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = &quot;Hello, world! &quot; + std::to_string(count_++);
    RCLCPP_INFO(this-&gt;get_logger(), &quot;Publishing: &#39;%s&#39;&quot;, message.data.c_str());
    publisher_-&gt;publish(message);
  }</code></pre><p>위의 코드는 timer_callback()함수가 어떤 기능을 하는지 말해준다. 
먼저 string타입의 메세지를 정의한후 그 메세지의 data에 &quot;Hello, world&quot;와 현재 카운트된 숫자를 조합해 저장한다. 
그다음 terminal에 publish될 내용이 뭔지 알수 있도록 log를 띄우는 RCLCPP_INFO를 사용하고, 마지막으로 미리 정의된 publisher가 방금 작성한 message를 publish하게 된다. </p>
<p>위의 코드를 살펴보면서 timer_, publisher_, count_들이 멤버변수로 사용됐는데 이는 다음과 같이 하단에 정의 되어있다. </p>
<pre><code>rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher&lt;std_msgs::msg::String&gt;::SharedPtr publisher_;
size_t count_;</code></pre><p>class의 생성자(constructor), 및 멤버 변수, 함수들이 정의 됐으면 이 클래스를 main함수에서 생성하여 기능하도록하는 코드를 다음과 같이 작성할 수 있다. </p>
<pre><code>int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared&lt;MinimalPublisher&gt;());
  rclcpp::shutdown();
  return 0;
}</code></pre><p>main함수에서는 rclcpp::init을 통해 ROS2 를 초기화 하고, rclcpp::spin으로 MinimalPublisher class에 정의된 publisher코드를 실행 시킨다. 
rclcpp::shutdown()은 중간에 프로그램이 종료하라는 명령이 들어오면(ctrl+c입력등) ROS2를 종료하고 0값을 return하고 프로그램을 종료한다. </p>
<h3 id="실습-22-dependency추가하기">실습 2.2. dependency추가하기</h3>
<p>우리가 작성한 C++ node를 build하기 위해선 CMakeLists.txt와 package.xml을 적절히 수정해야한다. </p>
<p>먼저 pakcage.xml을 visual code로 열어주자. </p>
<p>그 다음, description, maintainer, license를 적절히 입력해주고(빌드랑은 상관 없는 부분) 
다음과 같이 dependency를 입력해준다. </p>
<pre><code>&lt;depend&gt;rclcpp&lt;/depend&gt;
&lt;depend&gt;std_msgs&lt;/depend&gt;</code></pre><p>이는 우리 package는 rclcpp와 std_msgs를 dependecy로 갖는다는 말이다. </p>
<h3 id="실습-23-cmakeliststxt-수정하기">실습 2.3 CMakeLists.txt 수정하기</h3>
<p>이제 CMakeLists.txt를 열어서 rclcpp와 std_msgs 패키지를 찾는 문구를 다음과 같이 find_package(ament_cmake REQUIRED) 밑에 넣어준다. </p>
<pre><code>find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)</code></pre><p>이는 패키지를 빌드할 때 이 두 패키지를 찾으라는 의미이다. </p>
<p>그다음 우리가 작성한 publisher_member_function.cpp의 executable을 추가하고 이 executable은 rclcpp std_msgs에 의존성을 요구한다는 문구를 다음과 같이 추가한다. executable이름은 talker로 한다. </p>
<pre><code>add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)</code></pre><p>이제 install(TARGETS ...)문구를 작성하여 ros2 run 명령어로 해당 executable을 실행할 수 있게 해준다. </p>
<pre><code>install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})</code></pre><p>최종 CMakeLists.txt는 다음과 같다. </p>
<pre><code>cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES &quot;Clang&quot;)
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

ament_package()</code></pre><p>이제 패키지를 빌드 할 수 있지만 subscriber까지 작성하고 빌드하도록하자. </p>
<hr>
<h2 id="실습3-subscriber-node-작성하기">실습3. subscriber node 작성하기</h2>
<p>빠른 설명을 위해 먼저 subscriber node code를 다운받자. </p>
<pre><code>cd ~/ros2_ws/src/cpp_pubsub/src
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_subscriber/member_function.cpp</code></pre><p>그러면 ls를 했을 때 다음과 같은 subscriber_member_function.cpp가 생긴 것을 확인 할 수 있을 것이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/d11456ae-1a51-48bd-81e7-90bc1aaf79f3/image.png" alt=""></p>
<p>visual code로 subscriber_member_function.cpp를 열면 다음과 같은 코드를 볼 수 있다. </p>
<pre><code>#include &lt;memory&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;std_msgs/msg/string.hpp&quot;
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node(&quot;minimal_subscriber&quot;)
    {
      subscription_ = this-&gt;create_subscription&lt;std_msgs::msg::String&gt;(
      &quot;topic&quot;, 10, std::bind(&amp;MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String &amp; msg) const
    {
      RCLCPP_INFO(this-&gt;get_logger(), &quot;I heard: &#39;%s&#39;&quot;, msg.data.c_str());
    }
    rclcpp::Subscription&lt;std_msgs::msg::String&gt;::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared&lt;MinimalSubscriber&gt;());
  rclcpp::shutdown();
  return 0;
}</code></pre><h3 id="실습-31-코드-분석">실습 3.1 코드 분석</h3>
<p>코드를 하나씩 살펴보자. </p>
<pre><code>public:
  MinimalSubscriber()
  : Node(&quot;minimal_subscriber&quot;)
  {
    subscription_ = this-&gt;create_subscription&lt;std_msgs::msg::String&gt;(
    &quot;topic&quot;, 10, std::bind(&amp;MinimalSubscriber::topic_callback, this, _1));
  }</code></pre><p>먼저 이전에 살펴봤던 것처럼 생성자를 통해서 &quot;minimal_subscriber&quot;라는 이름을 갖는 node를 생성하게 한다. </p>
<p>생성자는 subscription을 string type을 subscript하게 정의하고 이 메세지는 &quot;topic&quot;이라는 이름을 갖는 topic에서 읽어온다. 
subscribe는 10개의 대기 메세지를 갖을 수 있으며, topic에서 message가 도착하면 topic_callback에 정의된 함수를 실행할 것이다. </p>
<pre><code>private:
  void topic_callback(const std_msgs::msg::String &amp; msg) const
  {
    RCLCPP_INFO(this-&gt;get_logger(), &quot;I heard: &#39;%s&#39;&quot;, msg.data.c_str());
  }
  rclcpp::Subscription&lt;std_msgs::msg::String&gt;::SharedPtr subscription_;</code></pre><p>그렇다면 topic_callback에 대한 정의가 필요한데, message가 들어오면 
어떤 메세지를 들었는지에 대한 메세지를 RCLCPP_INFO를 통해 출력하도록 코드를 작성했다. </p>
<p>또한 subscription_ 멤버 변수에 필요한 타입을 rclcpp::Subscription을 이용해 정의한다. </p>
<p>main함수에서는 publisher node에서와 마찬가지고 node를 생성하고 이를 spin으로 돌아가게 만든다. </p>
<h3 id="실습32-cmakelisttxt수정하기">실습3.2. CMakeList.txt수정하기</h3>
<p>publisher node를 작성했을 때 처럼 executable에 대한 구문과 install 구문을 넣어주어야한다. </p>
<pre><code>add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})</code></pre><p>executable 이름은 listener이고, 빌드할 때 rclcpp std_msgs를 필요로한다. </p>
<h2 id="실습4-빌드-및-실행하기">실습4. 빌드 및 실행하기</h2>
<p>빌드하기전에 항상 package.xml에 필요한 dependency를 설치하는 습관을 들이면 좋다. 이는 rosdep을 통해 쉽게 할 수 있다. </p>
<pre><code>cd ~/ros2_ws
rosdep install -i --from-path src --rosdistro humble -y</code></pre><p>그다음 colcon build해준다. cpp_pubsub만 빌드하면 되므로 pakcage-select tag를 추가해준다. </p>
<pre><code>colcon build --packages-select cpp_pubsub</code></pre><p>새로운 터미널을 열어 작업환경을 소스하고 talker node를 실행해준다. </p>
<pre><code>cd ~/ros2_ws
. install/setup.bash
ros2 run cpp_pubsub talker</code></pre><p>다음과 같은 메세지들이 출력되면 성공!</p>
<pre><code>[INFO] [minimal_publisher]: Publishing: &quot;Hello World: 0&quot;
[INFO] [minimal_publisher]: Publishing: &quot;Hello World: 1&quot;
[INFO] [minimal_publisher]: Publishing: &quot;Hello World: 2&quot;
[INFO] [minimal_publisher]: Publishing: &quot;Hello World: 3&quot;
[INFO] [minimal_publisher]: Publishing: &quot;Hello World: 4&quot;</code></pre><p>또 다른 터미널을 실행하여 listener node를 실행해준다. </p>
<pre><code>cd ~/ros2_ws
. install/setup.bash
ros2 run cpp_pubsub listener</code></pre><p>다음과 같이 publisher가 발행한 메세지를 받아 출력할 것이다. </p>
<pre><code>[INFO] [minimal_subscriber]: I heard: &quot;Hello World: 10&quot;
[INFO] [minimal_subscriber]: I heard: &quot;Hello World: 11&quot;
[INFO] [minimal_subscriber]: I heard: &quot;Hello World: 12&quot;
[INFO] [minimal_subscriber]: I heard: &quot;Hello World: 13&quot;
[INFO] [minimal_subscriber]: I heard: &quot;Hello World: 14&quot;</code></pre><hr>
<p>여태까지 간단한 publisher와 subscriber에 대한 코드 작성 예시를 살펴봤다. 
정말 단순하지만 timer callback이나 topic callback에 우리가 원하는 기능을 구현함으로써 유용하게 사용될 수 있는 개념이다. 
예를 들어 카메라 데이터가 발행되면(publisher) 사람을 인식해라(subscriber)라는 기능을 하는 node들도 오늘 배운 개념을 이용해서 작성할 수 있다. </p>
<p>이렇게 활용성이 크기 때문에 로봇개발할 때 가장 빈번하게 사용되는 개념이기도 하다. </p>
<p>다음시간에는 좀 더 특수한 기능을 갖는 service server와 client를 작성하는 방법에 대해 배워볼 것이다. </p>
<hr>
<p>오늘도 여기까지 온 스스로를 칭찬해주자!</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<ul>
<li><p>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a></p>
</li>
<li><p>오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a></p>
</li>
<li><p>IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">링크텍스트</a></p>
</li>
<li><p>참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html">링크텍스트</a></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/6900ea77-3ca7-467d-83a8-aef4a4a31fcb/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble에서 패키지 만들기]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 21 Jan 2024 03:31:53 GMT</pubDate>
            <description><![CDATA[<p>ROS2로 개발할 때 자주 듣게 되는 용어 중 하나는 package(패키지)이다. 이와 관련된 몇가지 실습을 진행하며 개념을 익혀보자.</p>
<hr>
<p>목표:
CMake나 Python으로 새로운 패키지를 만든다. 
만든 패키지의 executable(실행파일)을 실행한다. </p>
<p>tutorial level: 입문</p>
<p>예상 소요시간
약 15분</p>
<hr>
<h1 id="배경지식">배경지식</h1>
<h2 id="배경지식1--ros2-패키지란">배경지식(1) : ROS2 패키지란?</h2>
<p>패키지는 로봇기능들을 담당하는 코드들을 모아놓은 집합체이다. 
패키지를 이용하면 다른 사람들과 손쉽게 코드를 공유하고 설치할 수 있다. </p>
<p>ROS2에서 패키지를 만들기 위해 ament를 build system으로 사용하고 colcon을 build tool로 이용한다. 
CMake나 Python을 사용하여 package를 생성할 수 있는게 기본이지만 다른 방식도 사용가능하긴하다. </p>
<h2 id="배경지식2--ros2-패키지-구성요소">배경지식(2) : ROS2 패키지 구성요소</h2>
<p>앞서 말했듯이 ROS2 패키지는 CMake 와 Python이렇게 둘로 생성 가능하다. </p>
<p>무엇으로 만드는가에 따라 필요한 요소는 다음과 같다. </p>
<pre><code>CMake로 만드는 경우
- CMakeLists.txt 파일이 필요하다. 이 파일은 패키지내 코드를 어떻게 빌드할 것인지에 대한 설명을 담고 있다. 
- include/&lt;package_name&gt; 경로를 갖는다. 이 경로에는 패키지를 위한 public header파일들이 있다. 
- pakcage.xml 파일이 필요하다. 이 파일은 package와 관련된 meta 정보를 갖는데 이는 필요한 의존성을 갖는 패키지이름등을 포함한다. 
- src경로를 갖는다. 이 경로에는 package의 source code가 담겨있다. </code></pre><pre><code>Python로 만드는 경우
- pakcage.xml 파일이 필요하다. 이 파일은 package와 관련된 meta 정보를 갖는데 이는 필요한 의존성을 갖는 패키지이름등을 포함한다.  
- resource/&lt;package_name&gt; 마커 파일을 갖는다. 
- setup.cfg 가 필요한데 ros2 run 명령어가 패키지의 executable를 찾는데 도움을 준다. 
- setup.py 가 필요하다. 이 python파일은 pakcage를 어떻게 설치할 것인지에 대한 설치 방법이 포함되어 있다. 
- &lt;package_name&gt; 패키지와 같은 이름을 갖는 폴더이다. ROS2 tool을 이용하여 package를 찾는데 사용된다. __init__.py를 포함하고 있다. </code></pre><p>가장 간단한 파일구조는 각각 다음과 같다. </p>
<ul>
<li><p>CMake의 경우 </p>
<pre><code>my_package/
   CMakeLists.txt
   include/my_package/
   package.xml
   src/</code></pre></li>
<li><p>Python의 경우</p>
<pre><code>my_package/
    package.xml
    resource/my_package
    setup.cfg
    setup.py
    my_package/</code></pre></li>
</ul>
<h2 id="배경지식3-작업환경에서-패키지">배경지식(3) 작업환경에서 패키지</h2>
<p>하나의 작업공간에는 무수히 많은 패키지들이 있을 수 있다. 한 작업공간에 Python으로 만든 패키지든 CMake로 만든 패키지든 공존할 수 있다. 
그렇지만 패키지안에 패키지를 만드는(nesting)은 할 수 없다. </p>
<p>널리 사용되는 방식으로는 작업공간에 src폴더를 만들어 그 안에 package들을 위치시키는 것이다. </p>
<p>그러면 다음과 같은 파일 구조를 확인 할 수 있다. </p>
<pre><code>workspace_folder/
    src/
      cpp_package_1/
          CMakeLists.txt
          include/cpp_package_1/
          package.xml
          src/

      py_package_1/
          package.xml
          resource/py_package_1
          setup.cfg
          setup.py
          py_package_1/
      ...
      cpp_package_n/
          CMakeLists.txt
          include/cpp_package_n/
          package.xml
          src/</code></pre><h1 id="준비물">준비물</h1>
<ul>
<li>지난 시간에 workspace를 생성하는 실습을 통해 생성된 작업공간이 있으면 된다. 
아직 작업공간을 만드는 방법을 모르는 사람은 다음 링크 참고
<a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0">작업공간 만들기 실습 포스트</a></li>
</ul>
<h1 id="실습">실습</h1>
<h2 id="실습1-패키지-생성">실습1. 패키지 생성</h2>
<p>먼저 ROS2의 기본 workspace를 소스한다. </p>
<pre><code>source /opt/ros/humble/setup.bash</code></pre><p>지난 시간에 만든 ros2_ws/src로 이동 </p>
<pre><code>cd ~/ros2_ws/src</code></pre><p>다음 명령어를 통해 package생성할 수 있는데 기본 양식은 다음과 같다. </p>
<ul>
<li><p>CMake 명령어</p>
<pre><code>ros2 pkg create --build-type ament_cmake --license Apache-2.0 &lt;package_name&gt;</code></pre></li>
<li><p>Python 명령어</p>
<pre><code>ros2 pkg create --build-type ament_python --license Apache-2.0 &lt;package_name&gt;</code></pre></li>
</ul>
<p>이 둘의 차이점을 보면 ament_뒤에 cmake이냐, python이냐 인 것을 확인 할 수 있다. </p>
<p>명령어를 배웠으니 실제로 패키지를 만들어보자. </p>
<ul>
<li><p>CMake 명령어로 만들기</p>
<pre><code>ros2 pkg create --build-type ament_cmake --license Apache-2.0 --node-name my_node my_package</code></pre></li>
<li><p>Python 명령어로 만들기</p>
<pre><code>ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_node my_package</code></pre></li>
</ul>
<p>이 명령어를 살펴보면 my_package라는 패키지를 만들건데 my_node라는 이름을 갖는 node를 생성한다는 의미를 갖는다. </p>
<p>작업공간의 src폴더에 들어가면 my_package라는 폴더가 생성된것을 볼 수 있다. </p>
<p>패키지를 생성하는 명령어를 입력했을 때 다음과 같은 메세지가 터미널에 출력될 것이다. </p>
<ul>
<li><p>CMake 명령어 경우</p>
<pre><code>going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: [&#39;&lt;name&gt; &lt;email&gt;&#39;]
licenses: [&#39;TODO: License declaration&#39;]
build type: ament_cmake
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source and include folder
creating folder ./my_package/src
creating folder ./my_package/include/my_package
creating ./my_package/CMakeLists.txt
creating ./my_package/src/my_node.cpp</code></pre></li>
<li><p>Python 명령어 경우 </p>
<pre><code>going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: [&#39;&lt;name&gt; &lt;email&gt;&#39;]
licenses: [&#39;TODO: License declaration&#39;]
build type: ament_python
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source folder
creating folder ./my_package/my_package
creating ./my_package/setup.py
creating ./my_package/setup.cfg
creating folder ./my_package/resource
creating ./my_package/resource/my_package
creating ./my_package/my_package/__init__.py
creating folder ./my_package/test
creating ./my_package/test/test_copyright.py
creating ./my_package/test/test_flake8.py
creating ./my_package/test/test_pep257.py
creating ./my_package/my_package/my_node.py</code></pre></li>
</ul>
<p>log 메세지에 따르면 여러 파일들이 패키지 생성 명령어가 실행되면서 생성된 것을 확인 할 수 있다. </p>
<h2 id="실습2-패키지-빌드하기">실습2. 패키지 빌드하기</h2>
<p>작업공간에 패키지를 넣어놓으면 패키지가 여러개 있더라도 colcon을 이용해서 한번에 빌드할 수 있는 장점이 있다. </p>
<p>저번에 했던 것과 같이 빌드해보자. </p>
<pre><code>cd ~/ros2_ws
colcon build</code></pre><p>하나 알고 있으면 좋은 사실은 이렇게 colcon build를 입력하게 되면 현재 작업 공간에 있는 모든 패키지들(지난 시간에 설치한 ros_tutorials, turtlesim패키지등)이 같이 빌드 된다는 것이다. 즉 빌드 시간이 더 오래 걸릴 수 있다는 것을 의미한다. </p>
<p>만약에 방금 우리가 만든 my_package만 빌드하고 싶다면 다음과 같이 명령어를 변형할 수 있다. </p>
<pre><code>colcon build --packages-select my_package</code></pre><h2 id="실습3-setup-file-소스source하기">실습3. setup file 소스(source)하기</h2>
<p>저번시간에 배운 것 처럼 새롭게 빌드 및 설치한 패키지의 executable을 사용하려면 현재 작업공간을 source해줘야한다. </p>
<p>먼저 ROS2 기본 공간을 source하고</p>
<pre><code>source /opt/ros/humble/setup.bash</code></pre><p>그후 우리 작업공간을 source해주자.</p>
<pre><code>source install/local_setup.bash</code></pre><h2 id="실습4-패키지-사용하기">실습4. 패키지 사용하기</h2>
<p>--node-name으로 만든 node의 executable을 실행하기 위해 다음과 같이 ros2 run커맨드(명령어)를 활용할 수 있다. </p>
<pre><code>ros2 run my_package my_node</code></pre><p>CMake로 생성했는지 Python으로 생성했는지에 따라 다음과 같이 다른 메세지가 터미널에 출력될 것이다. </p>
<ul>
<li><p>CMake로 생성한 executable</p>
<pre><code>hello world my_package package</code></pre></li>
<li><p>Python으로 생성한 executable</p>
<pre><code>Hi from my_package.</code></pre></li>
</ul>
<h2 id="실습5-package-구성-확인하기">실습5. package 구성 확인하기</h2>
<p>ros2_ws/src/my_package로 이동하게 되면 ros2 pkg create명령어가 생성한 파일과 폴더들을 확인할 수 있다. </p>
<ul>
<li><p>CMake로 생성한 경우 폴더와 파일들</p>
<pre><code>CMakeLists.txt  include  package.xml  src</code></pre><p>주목할 점은 여기 src폴더 안에 my_node.cpp가 위치한다. 이 src폴더는 앞으로 우리가 작성할 custom C++ node들이 위치할 공간이다. </p>
</li>
<li><p>Python으로 생성한 경우 폴더와 파일들</p>
<pre><code>my_package  package.xml  resource  setup.cfg  setup.py  test</code></pre><p>비슷하게 my_node.py는 my_package안에 위치한다. 이 패키지 이름과 같은 폴더의 경로에 앞으로 우리가 작성할 custom python node들이 위치할 것이다. </p>
</li>
</ul>
<h2 id="실습6-packagexml-커스터마이즈하기">실습6. package.xml 커스터마이즈하기</h2>
<p>관찰력이 높은 사람이라면 ros2 pkg create 명령어로 패키지를 만들 때 TODO: 가 출력된 부분을 확인 했을 것이다. </p>
<pre><code>...
description: TODO: Package description
maintainer: [&#39;&lt;name&gt; &lt;email&gt;&#39;]
licenses: [&#39;TODO: License declaration&#39;]
...</code></pre><p>이 description, maintainer, licenses 정보는 자동으로 결정되지 않지만 나중에 이 패키지를 배포하고 싶을 때 필요한 정보라서 ros2 pkg create명령어는 자동으로 공간만 일단 만들어놓고 내용을 의미없게 채워놓은 것이다. </p>
<p>이를 의미있게 바꾸려면 package.xml에 들어가서 바꿔주면 된다. </p>
<pre><code>cd ~/ros2_ws/src/my_package
gedit package.xml</code></pre><ul>
<li><p>CMake로 만든 경우</p>
<pre><code>&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;?xml-model
 href=&quot;http://download.ros.org/schema/package_format3.xsd&quot;
 schematypens=&quot;http://www.w3.org/2001/XMLSchema&quot;?&gt;
&lt;package format=&quot;3&quot;&gt;
&lt;name&gt;my_package&lt;/name&gt;
&lt;version&gt;0.0.0&lt;/version&gt;
&lt;description&gt;TODO: Package description&lt;/description&gt;
&lt;maintainer email=&quot;user@todo.todo&quot;&gt;user&lt;/maintainer&gt;
&lt;license&gt;TODO: License declaration&lt;/license&gt;

&lt;buildtool_depend&gt;ament_cmake&lt;/buildtool_depend&gt;

&lt;test_depend&gt;ament_lint_auto&lt;/test_depend&gt;
&lt;test_depend&gt;ament_lint_common&lt;/test_depend&gt;

&lt;export&gt;
 &lt;build_type&gt;ament_cmake&lt;/build_type&gt;
&lt;/export&gt;
&lt;/package&gt;</code></pre></li>
</ul>
<ul>
<li><p>Python으로 만든경우 </p>
<pre><code>&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;?xml-model
 href=&quot;http://download.ros.org/schema/package_format3.xsd&quot;
 schematypens=&quot;http://www.w3.org/2001/XMLSchema&quot;?&gt;
&lt;package format=&quot;3&quot;&gt;
&lt;name&gt;my_package&lt;/name&gt;
&lt;version&gt;0.0.0&lt;/version&gt;
&lt;description&gt;TODO: Package description&lt;/description&gt;
&lt;maintainer email=&quot;user@todo.todo&quot;&gt;user&lt;/maintainer&gt;
&lt;license&gt;TODO: License declaration&lt;/license&gt;

&lt;test_depend&gt;ament_copyright&lt;/test_depend&gt;
&lt;test_depend&gt;ament_flake8&lt;/test_depend&gt;
&lt;test_depend&gt;ament_pep257&lt;/test_depend&gt;
&lt;test_depend&gt;python3-pytest&lt;/test_depend&gt;

&lt;export&gt;
 &lt;build_type&gt;ament_python&lt;/build_type&gt;
&lt;/export&gt;
&lt;/package&gt;</code></pre></li>
</ul>
<p>위에서 </p>
<pre><code>&lt;description&gt;TODO: Package description&lt;/description&gt;
 &lt;maintainer email=&quot;user@todo.todo&quot;&gt;user&lt;/maintainer&gt;
 &lt;license&gt;TODO: License declaration&lt;/license&gt;</code></pre><p>부분에 해당하는 내용을 바꿔주면 된다. </p>
<p>다음과 같이 변경해보겠다. </p>
<pre><code>&lt;description&gt;Beginner client libraries tutorials practice package&lt;/description&gt;
&lt;maintainer email=&quot;irobou@gmail.com&quot;&gt;user&lt;/maintainer&gt;
&lt;license&gt;Apache License 2.0&lt;/license&gt;</code></pre><p>그 다음 변경사항을 ctrl+s를 눌러 저장해준다. </p>
<p>추가로 package.xml에서 알아야 할 것은 tag 이름들중에 &quot;_depend&quot; 로 끝나는 태그들이 있는데 이는 우리 패키지가 어떤 패키지에 의존하는지를 나열하는데 사용한다. 
여기에 나열해놓음으로써 colcon이 빌드할 때 어떤 의존 패키지를 찾아야하는지 알 수 있다. 
my_package 같은 경우에는 단순한 패키지라 특별한 dependency를 갖지 않는다. 
그렇지만 앞으로 배울 tutorial에 사용되기위해 해당 공간은 몇가지 dependency를 나열하고 있다. </p>
<p>Python으로 생성한 경우 한가지 작업을 더해줘야 하는데 바로 setup.py에서 우리가 package.xml에서 수정한 부분에 해당하는 곳을 변경하는 일이다. </p>
<p>먼저 setup.py를 편집기로 열어준다. </p>
<pre><code>cd ~/ros2_ws/src/my_package
gedit setup.py</code></pre><pre><code>from setuptools import setup

package_name = &#39;my_py_pkg&#39;

setup(
 name=package_name,
 version=&#39;0.0.0&#39;,
 packages=[package_name],
 data_files=[
     (&#39;share/ament_index/resource_index/packages&#39;,
             [&#39;resource/&#39; + package_name]),
     (&#39;share/&#39; + package_name, [&#39;package.xml&#39;]),
   ],
 install_requires=[&#39;setuptools&#39;],
 zip_safe=True,
 maintainer=&#39;TODO&#39;,
 maintainer_email=&#39;TODO&#39;,
 description=&#39;TODO: Package description&#39;,
 license=&#39;TODO: License declaration&#39;,
 tests_require=[&#39;pytest&#39;],
 entry_points={
     &#39;console_scripts&#39;: [
             &#39;my_node = my_py_pkg.my_node:main&#39;
     ],
   },
)</code></pre><p>위에서 description, license, maintainer, maintainer이름을 package.xml에서 변경한대로 바꿔준다. </p>
<pre><code>예시
maintainer=&#39;irobou&#39;,
maintainer_email=&#39;irobou@gmail.com&#39;,
description=&#39;Beginner client libraries tutorials practice package&#39;,
license=&#39;Apache License 2.0&#39;,</code></pre><p>ctrl+s를 눌러 저장한다. </p>
<hr>
<p>여태까지 ROS2 custom package를 생성하고 그 내부 구성에 대해 살펴봤다. </p>
<p>요약하자면 package는 코드모음이고 쉽게 남들과 공유 가능하다. 
ros2 pkg create 명령어는 ROS2 package에 필요한 폴더 및 파일들을 만들어준다. 
만든 패키지를 빌드할 땐 colcon build를 사용할 수 있으며
빌드한 후 사용하기 위해선 작업공간을 source해줘야 한다. </p>
<p>다음시간에는 ROS2의 꽃 (c++)로 publisher와 subscriber를 작성하는 방법을 알아볼 것이다. </p>
<hr>
<p>오늘도 여기까지 온 스스로를 칭찬해주자!</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<ul>
<li>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a></li>
<li>오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a></li>
<li>IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">링크텍스트</a></li>
<li>참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html#id10">링크텍스트</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/35fdfa68-2476-4a6e-83ea-1bf690a681cb/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble에서 작업공간 생성하기]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 03 Dec 2023 08:21:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/i_robo_u/post/804443fa-963c-4c64-904f-9924fcc6b528/image.png" alt=""></p>
<p>ROS2로 개발할 때 자주 듣게 되는 용어 중 하나는 workspace(작업 공간)이다. 이와 관련된 몇가지 실습을 진행하며 개념을 익혀보자. </p>
<hr>
<p><strong>목표:</strong> </p>
<ul>
<li>workspace를 생성한다. </li>
<li>개발 및 테스트을 위한 overlay 설정 방법에 대해 배운다. </li>
</ul>
<p><strong>tutorial level</strong>: 입문</p>
<p><strong>예상 소요시간</strong>
약 20분</p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>workspace는 여러 ROS2 package를 담고 있는 디렉토리이다. 보통 ROS2에서 제공하는 패키지를 이용하려면 일단 터미널에서 ROS2 installation workspace를 source 해야한다. </p>
<p>ROS2에서 제공하는 installation workspace이외에도 다른 workspace를 source할 수 있는데 이를 기존의 ROS2 workspace(underlay)에 overlay한다고 한다. 
이때 underlay는 overlay의 모든 패키지의 dependencies를 포함하고 있어야한다. </p>
<p>이름에서 처럼 overlay에 있는 패키지들은 underlay에 있는 패키지들을 override하게 되는데 이는 underlay의 패키지를 무효로하고 overlay의 패키지를 우선한다고 생각하면된다. </p>
<p>또한 underlay위의 overlayer, 이의 overlayer, 또 이 위의 overlayer의 형태를 갖을 수 있기도 하다. </p>
<p>쉽게 생각하면 작업공간을 한층 한층 점차 확장 할 수 있다는 의미이다.</p>
<h2 id="준비물">준비물</h2>
<ul>
<li>ROS2 설치</li>
<li>colcon 설치</li>
<li>git 설치</li>
<li>turtlesim 설치</li>
<li>rosdep 설치</li>
<li>터미널 커맨드 기초</li>
<li>text 편집기</li>
</ul>
<h2 id="실습">실습</h2>
<h3 id="1-ros2-환경-source-하기">1. ROS2 환경 source 하기</h3>
<p>이 튜토리얼에서는 ROS2 installation workspace가 underlay로써 역할을 한다. 다만, ROS2 installation workspace가 항상 underlay일 필요는 없다는 것을 참고하자. </p>
<p>Linux OS에서 설치했다면 다음 명령어를 통해 ROS2 installation workspace를 source할 수 있다. </p>
<pre><code>$ source /opt/ros/humble/setup.bash</code></pre><h3 id="2-새로운-directory-생성하기">2. 새로운 directory 생성하기</h3>
<p>기본적으로 한 directory당 하나의 workspace를 배정하면 좋다. directory이름은 크게 상관 없지만 해당 workspace의 용도를 가리킬 수 있는 이름으로 해두면 나중에 여러 workspace가 있을 때 &quot;아, 이 workspace는 이 용도였지~&quot;라고 기억 할 수 있게 된다. </p>
<p>이번 실습에서는 ros2_ws라는 workspace를 다음과 같이 만들어 사용하고자 한다. </p>
<pre><code>$ mkdir -p ~/ros2_ws/src
$ cd ~/ros2_ws/src</code></pre><p>위의 명령어의 경우 ros2_ws라는 workspace directory 밑에 src라는 directory를 만드는 것을 볼 수 있다. </p>
<p>이때 src는 앞으로 사용될 ROS2 패키지들이 있게될 directory이다. </p>
<h3 id="3-sample-repository-복사하기">3. sample repository 복사하기</h3>
<p>ros2_ws/src 경로에 위치한 후, 다음 명령어를 통해 humble branch를 복사한다. </p>
<pre><code>$ git clone https://github.com/ros/ros_tutorials.git -b humble</code></pre><p>위의 명령어를 실행 시키면 ros_tutorials이라는 repository가 복사되는데 이는 turtlesim 이라는 패키지를 포함하고 있다. 
다른 패키지들도 포함되어있지만 COLCON_IGNORE 파일이 포함되어 있어서 빌드시 무시된다는 점을 참고하자. </p>
<p>여태까지 ros2_ws라는 workspace를 만들고, turtlesim이라는 sample package도 복사했다. 
그런데 dependency(sample package를 빌드하는데 필요한 다른 패키지등)을 아직 체크하지 않았기 때문에 sample package가 온전히 빌드될 것인지 보장할 수 없다. </p>
<p>그럼 이는 어떻게 확인하고 해결할 수 있을까?
다음 실습에서 배워보자!</p>
<h3 id="4-dependency-문제-확인-및-해결하기">4. Dependency 문제 확인 및 해결하기</h3>
<p>우리가 복사한 sample package turtlesim의 dependency는 ROS2를 설치할 때 전부다 설치됐을 수 있지만, 항상 package를 빌드하기전에 확인하는 습관을 만들어 놓으면 좋다. </p>
<p>일단 다음 명령어로 ros2_ws로 이동한 후 rosdep을 이용하여 필요한 ros dependency를 모두 확인 하고 설치하라는 명령어를 실행한다. </p>
<pre><code># cd if you&#39;re still in the ``src`` directory with the ``ros_tutorials`` clone
$ cd ~/ros2_ws
$ rosdep install -i --from-path src --rosdistro humble -y</code></pre><p>필요한 dependency가 없다면 설치할 것이고 이미 다 설치됐다면 다음과 같은 메세가 출력될 것이다. </p>
<pre><code>#All required rosdeps installed successfully</code></pre><p>간단히 rosdep 명령어의 동작원리를 설명하자면 ros2 package는 package.xml이라는 파일을 포함하고 있는데 rosdep 명령어는 이 파일에 명시된 dependency들을 확인하고 아직 설치되어있지 않다면 설치한다. </p>
<p>package.xml과 rosdep에 대한 좀 더 자세한 설명은 다른 포스트에서 다룰 예정이니 많은 관심 바란다! </p>
<h3 id="5-colcon으로-workspace-빌드하기">5. colcon으로 workspace 빌드하기</h3>
<p>dependency까지 해결 했다면 다음 명령어를 통해 ros2_ws를 빌드할 수 있다. </p>
<pre><code>$ cd ~/ros2_ws
$ colcon build</code></pre><p>성공적으로 빌드 됐다면 다음과 같은 메세지를 출력하게된다. </p>
<pre><code>Starting &gt;&gt;&gt; turtlesim
Finished &lt;&lt;&lt; turtlesim [5.49s]

Summary: 1 package finished [5.58s]</code></pre><p>빌드가 끝나면 ls 명령어를 통해 다음과 같은 build, install, log와 같은 directory가 생성된 것을 확인할 수 있다. </p>
<pre><code>$ cd ~/ros2_ws
$ ls</code></pre><pre><code>build  install  log  src</code></pre><p>위에서 생성된 install directory는 overlay를 source할 때 필요한 file들이 저장되어있는 곳이다. </p>
<h3 id="6-overlay-불러오기source하기">6. overlay 불러오기(source하기)</h3>
<p>build를 진행했던 같은 terminal에서 overlay를 불러오게되면 예기치 못한 문제가 생길 수 있기 때문에, overlay를 불러오기 전에 build했던 terminal과 다른 터미널을 열어줘야 한다.</p>
<p>새로운 terminal에서 기존의 ROS2 환경을 source하면 &quot;underlay&quot;가 되게 되고 이후에 새롭게 build한 환경을 source하게 되면 &quot;underlay&quot;위에 &quot;overlay&quot;가 되게 된다.</p>
<p>이렇게 overlay와 underlay를 만드는 명령어는 다음과 같다. </p>
<pre><code>#ROS2환경을 underlay로 만든다. 
$ source /opt/ros/humble/setup.bash</code></pre><pre><code>$cd ~/ros2_ws
#ROS2환경 위에 새롭게 빌드한 환경을 overlay로 만든다. 
$ source install/local_setup.bash</code></pre><p><strong>&lt;참고&gt;
local_setup 과 setup의 차이</strong></p>
<ul>
<li>local_setup의 경우 overlay에 있는 package만 작업환경에 추가한다. </li>
<li>반면에 setup의 경우 overlay에 있는 package와 overlay를 build할 때 사용된 underlay도한 작업환경에 추가하게 된다. </li>
<li>따라서 위처럼 ROS2 installation의 setup(underlay의 package가 이용가능해짐)과 ros2_ws의 overlay(overlay의 package가 이용가능해짐)의 local_setup을 source하게 되면, ros2_ws의 setup(overlay와 overlay를 build할 때 사용된 underlay 둘다 사용가능해짐) 을 source한 것과 마찬가지이다. </li>
</ul>
<p>이제 드디어 source한 overlay로부터 turtlesim을 실행할 수 있다. </p>
<pre><code>$ ros2 run turtlesim turtlesim_node</code></pre><p>그런데 의문점이 하나 있다. 
어떻게 지금 실행된 turtlesim이 underlay것이 아니고 overlay에서 실행된 것인지 알 수 있을까? </p>
<p>overlay에 있는 turtlesim을 조금 변형시켜서 변화가 일어나면 overlay의 turtlesim이라는 것을 알 수 있다. </p>
<p>몇가지 실험을 해보자. </p>
<h3 id="7-overlay-변경하기">7. overlay 변경하기</h3>
<p>이번에는 overlay에 있는 turtlesim을 변경해보자한다. turtlesim을 실행했을 때 윈도우에 있는 타이틀을 변경해보자. </p>
<p>먼저 turtle_frame.cpp파일을 수정하기 위해 올바른 directory로 다음 명령어를 통해 이동하자. </p>
<pre><code>$cd ~/ros2_ws/src/ros_tutorials/turtlesim/src</code></pre><p>ls를 해보면 turtle_frame.cpp 파일이 있는데 해당 파일을 열어보자. </p>
<pre><code>$ls
$gedit turtle_frame.cpp</code></pre><p>52번째 줄을 보면  </p>
<blockquote>
<p>setWindowTitle(&quot;TurtleSim&quot;);</p>
</blockquote>
<p>이라는 함수가 있는데, &quot;TurtleSim&quot;을 &quot;MyTurtleSim&quot;으로 바꿔보자. </p>
<p>그다음 colcon build를 했던 터미널로 돌아와 다시 colcon build를 실행한다. </p>
<pre><code>$colcon build</code></pre><p>빌드를 끝마쳤다면 빌드에 사용한 터미널이 아닌 overlay를 source한 두번째 터미널로 와서 turtlesim을 실행한다. </p>
<pre><code>$ros2 run turtlesim turtlesim_node</code></pre><p>다음과 같이 turtlesim 창에서 &quot;MyTurtleSim&quot;이라는 타이틀을 확인할 수 있을 것이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/90c556c6-88e7-408d-8b9b-bdbc86324355/image.png" alt=""></p>
<p>overlay와 underlay에 대한 실험을 하나해보자. </p>
<p>새로운 터미널을 열어서 ROS2 installation 작업환경만 source해보자. </p>
<pre><code>$source /opt/ros/humble/setup.bash</code></pre><p>그 후 turtlesim을 실행하게 되면 다음과 같은 창이 뜬다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/a5d9fe2d-f294-4db4-9c17-41b6aa3be05c/image.png" alt=""></p>
<p>왜 &quot;MyTurtleSim&quot;이 아닐까?</p>
<p>이는 overlay와 underlay의 개념을 생각해보면 알 수 있다. 
&quot;MyTurtleSim&quot;가 뜨는 terminal의 작업환경에서는 우리의 작업공간이 overlay이기 때문에 underlay에 있는 turtlesim보다 우선순위로 여겨졌기 때문에 &quot;TurtleSim&quot;대신 &quot;MyTurtleSim&quot;이 창 타이틀에 표시되는 것이다. </p>
<p>반대로 ROS2 installation 작업공간만 source한 경우 해당 작업공간에 있는 turtlesim이 최우선적으로 적용되게 된다. </p>
<p>또한 이는 overlay에 있는 것을 작업한다고 underlay에 있는 같은 이름의 node 또는 package에 아무 영향이 없다는 것을 의미한다. </p>
<h3 id="마무리">마무리</h3>
<p>이번시간에는 작업공간에 대한 underlay와 overlay에 대해 배웠다. 
overlay는 underlay보다 더 높은 우선순위를 있는 것을 위의 예제를 통해 배웠다. </p>
<p>또한 overlay 작업공간을 이용하면 기존의 작업공간과 독립적으로 작업할 수 있기 때문에 기능 단위로 작업공간을 생성하는 것을 추천한다. 
이런 습관을 들이면 한 작업공간에 수많은 pakcage들이 있는 것을 예방할 수 있어서 관리차원에서 수월하다. </p>
<p>다음시간에는 pakcage를 만드는 실습을 해보도록 하겠다. </p>
<hr>
<p>오늘도 여기까지 온 스스로를 칭찬해주자!</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">링크텍스트</a>
참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.html">링크텍스트</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/b12235e7-917c-40e4-a67b-e3bea89b5466/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble에서 colcon으로 ROS2 workspace(작업공간) 구성하기]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-colcon%EC%9C%BC%EB%A1%9C-ROS2-workspace%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble%EC%97%90%EC%84%9C-colcon%EC%9C%BC%EB%A1%9C-ROS2-workspace%EC%9E%91%EC%97%85%EA%B3%B5%EA%B0%84-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Oct 2023 08:45:22 GMT</pubDate>
            <description><![CDATA[<p>ROS2를 이용해 로봇 개발을 하다보면 package라는 용어를 많이 접하게 된다. 
이 package는 무엇을 의미할까? 
ROS2에서 packge란 노드, 라이브러리, 런치 파일 및 다른 여러 정보,파일 등을 하나로 묶는 구조라고 생각할 수 있다. </p>
<p>예를 들어 4개 바퀴 달린 로봇이 자율주행하는 기능을 실행하는 &quot;auto_driving&quot;이라는 package가 있다고 가정해보자. 
이 package내에는 길 찾는 node, 바퀴를 제어하는 node등이 있을 수 있다. 
또한 이 node들이 동작하기 위해 어떤 기능들을 모아놓은 라이브러리들이 있을 수 있다. 
더불어 node를 실행하기 위해 런치파일들이 작성되어 있을 수 있다. </p>
<p>이 처럼 큰 기능(또는 프로그램)을 수행하기 위해 필요한 정보 및 작은 기능들이 모여있는 구조를 package라고 한다. </p>
<p>다소 추상적이므로 앞으로 개발하면서 차차 알아가면 되기 때문에 지금 와닿지 않아도 크게 걱정하지 않아도 된다! </p>
<p>일단 큰 기능을 위해 작은 기능을 모아놓은 구조 정도로 받아드리고 넘어가자. </p>
<p>그렇다면 이제 이런 package를 만들기 위해 필요한 개념들을 하나씩 배워보자. </p>
<p>이번엔 colcon으로 ROS2 작업공간을 구성하는 것부터 배워보겠다. </p>
<p>Let&#39;s go! </p>
<hr>
<p><strong>목표</strong>
colcon을 이용해 ROS2 workspace(작업공간)을 만든다. </p>
<p><strong>예상 소요시간</strong>
20분 내외</p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>colcon은 Concept of Libraries for Compilers Installation의 약자이다. Concept의 CO, Libraries의 L, Compilers의 CO, Installation의 N을 따서 만들었다. </p>
<p>colcon은 ROS2와 관련된 pakcage를 관리하는데 사용되는 도구인데, colcon을 이용하면 ROS2 작업공간을 설정하고 패키지 빌드할 수 있고, 패키지의 의존성 관리, 패키지 설치, 런치 파일 관리등 다양항 작업들을 주행 할 수 있다.  </p>
<p>지금은 어려울 수 있으니, 그냥 colcon을 &quot;아 패키지를 만들 때 사용되는 구나&quot; 정도로 받아드리고 넘어가면 되겠다. </p>
<hr>
<h1 id="준비물">준비물</h1>
<h2 id="colcon-설치">colcon 설치</h2>
<p>colcon을 사용하기 위해선 설치를 먼저 해야한다. 
다음과 같은 명령어를 터미널에 입력한다. </p>
<pre><code>sudo apt install python3-colcon-common-extensions</code></pre><h1 id="1-기초-지식-및-실습">1. 기초 지식 및 실습</h1>
<p>ROS2의 작업 공간(폴더와 파일들로 이루어져있다고 생각하면 된다)은 다소 특이한 구조를 따른다. </p>
<p>ROS2 작업공간 폴더이름을 &quot;ros2_colcon_practice_ws&quot;라고 이름 짓게 되면 이 폴더 안에는 &quot;src&quot;라는 폴더를 갖고 있기 마련이다. </p>
<p>예시)</p>
<pre><code>ros2_colcon_practice_ws
-src</code></pre><p>이때 &quot;src&quot;는 ROS2 package의 소스코드를 갖는 폴더이다. 최초 작업공간을 만들었을 때 이 src폴더는 아무 것도 없다. </p>
<p>colcon은 &quot;out of source builds&quot;(소스코드를 빌드하여 얻어진 실행파일, 라이브러리등의 결과물을 분리하여 관리하는 방식. 즉 다른 폴더에 결과물이 저장된다.)을 사용한다.</p>
<p>따라서 colcon으로 build하게 되면 &quot;build&quot;, &quot;install&quot;, &quot;log&quot;와 같은 폴더들이 생기게 된다. </p>
<pre><code>ros2_colcon_practice_ws
-src
-build
-install
-log</code></pre><p>생성되는 각 폴더는 다음과 같은 역할을 담당한다. </p>
<ul>
<li><p>build: intermediate 파일들이 저장되는 경로이다. 작업 공간내에 각 package(CMake 가 있는 공간)에 해당하는 폴더들이 생성된다. 이 파일들은 라이브러리나 최종 실행 파일들을 생성하는데 필요하다. </p>
</li>
<li><p>install: 각 package들이 설치되는 경로이다. default옵션은 package별로 다른 경로에 생성된다. lib나 share같은 결과물들이 들어 있다. </p>
</li>
<li><p>log: colcon이 일하는 동안 각종 log들을 저장하는 경로이다. </p>
</li>
</ul>
<p>어느 정도 작업공간에 대한 용어를 알아보았으니 간단한 실습을 진행해보자.
사실 지금 단계에서 각 폴더에 무엇이 담겨있는지 몰라도 크게 상관 없다. </p>
<h2 id="11-workspace작업공간-생성하기">1.1. workspace(작업공간) 생성하기</h2>
<ul>
<li>먼저 폴더를 다음과 같이 만든다. <pre><code>mkdir -p ~/ros2_ws
cd ~/ros2_ws</code></pre><h2 id="12-소스코드-파일들-추가하기">1.2. 소스코드 파일들 추가하기</h2>
</li>
<li>git clone을 통해 예시 소스코드 파일들을 복사해오자. 지금 git clone에 대해 잘 몰라도 괜찮다. 간단하게 설명하자면 폴더 및 파일들이 저장되어 있는 곳으로부터 복사해온다고 생각하면 쉽다. <pre><code>git clone https://github.com/ros2/examples src/examples -b humble</code></pre></li>
</ul>
<p>git clone이 끝났다면 다음과 같은 폴더 및 파일 구조를 갖을 것이다. </p>
<pre><code>ros2_ws
└── src
    └── examples
        ├── CONTRIBUTING.md
        ├── LICENSE
        ├── rclcpp
        ├── rclpy
        └── README.md

4 directories, 3 files</code></pre><h3 id="121-짜투리-지식">1.2.1. 짜투리 지식</h3>
<p>source an underlay와 overlay</p>
<p>-source an underaly: 우리가 ROS2를 설치했을 때 ROS2의 기본 작업환경을 .bashrc 파일에 &quot;source /opt/ros/humble/setup.bash&quot; 형태로 source했던 것을 일반적으로 &quot;underlay를 source했다&quot;고 한다. </p>
<ul>
<li>source an overlay: ROS2의 기본 작업 환경을 source했으면 우리 개인이 만드는 (상대적으로 작은 규모의) 작업환경을 source하는 것을 &quot;overlay를 source 한다&quot;라고 표현한다. </li>
</ul>
<p>이렇게 작업환경들을 source하는 이유는 서로 의존성이 있을 수 있는 파일들에게 &quot;야 너가 지금 쓰려고 하는거 저기 다른 작업환경에 있어&quot;라는 식으로 알려줄 수 있기 때문이다. </p>
<h2 id="13-작업환경-build하기">1.3. 작업환경 build하기</h2>
<p>작업환경을 build하기 위해선 &quot;colcon build&quot;라는 명령어를 사용할 수 있다. 
ros2_ws 경로로 이동하여 다음 명령어를 입력하자. </p>
<pre><code>cd ~/ros2_ws
colcon build --symlink-install</code></pre><p>근데 symlink-install이라는 인자가 보이는데 뭐하는 것일까? 이는 Python같은 compile하지 않는 파일들을 단순히 수정하므로써 컴파일 없이 변경내용이 적용 될 수 있도록 해준다. </p>
<p>build가 끝나면 다름과 같이 폴더(경로)들이 생성된다. </p>
<pre><code>ros2_ws
├── build
├── install
├── log
└── src

4 directories, 0 files</code></pre><h2 id="14-test-해보기">1.4. test 해보기</h2>
<p>colcon을 이용하면 방금 build한 package들을 테스트 해볼 수 있다. </p>
<pre><code>colcon test</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/01188314-7eb9-4b36-9a3b-4b457d1e8a0c/image.png" alt=""></p>
<p>test가 완료 됐다면 다음과 같은 메세지를 확인할 수 있다. </p>
<h2 id="15-작업환경-source하기">1.5. 작업환경 source하기</h2>
<p>아까 말했듯이 install경로에는 package를 build한 결과물 lib, share, 실행파일등이 담겨있다. 실행 파일 및 library를 사용하려면 이들을 path및 library path에 포함시켜야 한다. 
이렇게 포함 시키는 것을 작업환경을 source한다고 한다. 
이렇게 source하기 위해서는 install폴더에 있는 setup.bash를 이용하면 된다. 이 setup.bash는 colcon으로 package를 build할 때 생성된다. </p>
<p>다음 명령어를 터미널에 입력한다. </p>
<pre><code>source install/setup.bash</code></pre><h2 id="16-demo-실행해보기">1.6. demo 실행해보기</h2>
<p>사용하고자 하는 라이브러리 및 실행 파일들이 담겨 있는 작업환경을 source했다면 이제 실행 실습을 해보자. </p>
<p>다음과 같은 명령어로 &quot;examples_rclcpp_minimal_subscriber&quot;라는 package에 있는 &quot;subscriber_member_function&quot; node를 실행해보자. </p>
<pre><code>ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function</code></pre><p>그 다음 새로운 터미널을 열고 작업환경을 source한다음 다음 publisher_member_function이라는 node를 실행해보자. </p>
<pre><code>cd ~/ros2_ws
source install/setup.bash
ros2 run examples_rclcpp_minimal_publisher publisher_member_function</code></pre><p>그러면 다음과 같이 publisher node에서 message를 publish하고, subscriber node에서 해당 message를 subscribe하는 것을 다음 이미지와 같이 확인 할 수 있다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/ec23f5d8-6d1b-4c2d-857f-d13e4af249a2/image.png" alt=""></p>
<hr>
<h1 id="2-내-package-생성하기">2. 내 package 생성하기</h1>
<p>ros2 pkg create 명령어를 사용하면 새로운 기본 ros2 package를 생성할 수 있다. </p>
<p>보통 새로운 package는 작업공간 폴더 밑에 있는 src안에서 만들어지는게 통상적이다. </p>
<p>다음과 같이 만들어보자</p>
<pre><code>cd ~/ros2_ws/src
ros2 pkg create my_package_name
cd my_package_name
ls</code></pre><p>그러면 다음과 같이 파일이 형성 된 것을 볼 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/94f83556-6680-4e7f-a180-513fb6d9ed48/image.png" alt=""></p>
<p>이렇게 생성된 새로운 패키지(지금은 아주 기본적인 파일 및 폴더들만 있고 아무 기능이 구현되있지 않다)는 앞서 배운 것 처럼 colcon build --symlink-install로 build가능하다. </p>
<p>build하게 되면 다음과 같이 패키지 수가 22개에서 23개로 증가한 것을 확인 할 수 있다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/5d45e6f9-cc4d-4a14-9d16-a0b2c95554cf/image.png" alt=""></p>
<hr>
<p>여태까지 colcon을 이용해 package를 build하는 법을 알아보았다. 
이번 블로그에서는 git clone을 통해 남이 이미 만들어놓은 package를 build했고 마지막에 아주 기본 구조만 갖춘 나만의 package를 만들어 build해보았다. </p>
<p>나중에 package내에 기능들(topic, service 등)을 구현해서 build해볼 예정이니 지금은 명령어가 이런게 있구나~ 하고 넘어가면된다. </p>
<p>오늘도 여기까지 온 스스로를 칭찬해주자!</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a>
참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Colcon-Tutorial.html">https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Colcon-Tutorial.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/6f9e1508-1172-406d-82b8-b677a1069455/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble Action 이해하기, ROS2 명령어-6]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Action-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-6</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Action-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-6</guid>
            <pubDate>Sat, 30 Sep 2023 15:07:01 GMT</pubDate>
            <description><![CDATA[<p>ROS2로 개발할 때 topic과 service 만큼 자주 쓰이지 않지만, 특정 기능 을 구현 할 때 사용하면 깔끔하게 구현을 도와주는 개념이 있다. 
바로 action이라는 개념인데, ROS2 Navigation stack에서도 자주 사용되는 개념이므로 나중에 직접 구현하여 사용하지 않더라도, 한번 어떤거지 배워 놓으면 도움 된다. </p>
<p>그렇다면 Let&#39;s go!</p>
<hr>
<p><strong>목표</strong>
ROS2 action 에 대해 </p>
<p><strong>예상 소요시간</strong>
15분 </p>
<hr>
<h1 id="배경지식">배경지식</h1>
<p>topic과 service처럼 action은 ROS2에서 사용되는 통신 방법중 하나다. 
action은 goal(목표로 하는 행위), feedback(행위가 이루어지는 동안 피드백), result(결과)로 구성돼있다. </p>
<p>하단에 첨부된 그림과 같이 action은 service(goal에 대한 Goal Service, result에 대한 Result Service)와 topic(feedback을 주는 Feedback Topic)을 갖추고 있다. 그렇기 때문에 service와 유사하다. 그렇지만 service와 다르게 action은 중간에 취소가능하고 goal을 위해 어떤 행위를 취하는 동안 그에 따른 진행사항(피드백)을 말해준다. </p>
<p>Action은 client-server 구조를 사용한다.
즉, &quot;Action Client&quot; node는 &quot;Action Server&quot; node로 goal을 보내고 Action Server는 이를 받아 행동을 수행하면서 피드백을 그림과 같이 보내고 결과 적으로 행위가 잘 이루어졌는지에 대한 결과를 반환한다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/e5a24872-4f62-4ef2-a0aa-448382323a46/image.gif" alt=""></p>
<hr>
<h1 id="준비물">준비물</h1>
<ul>
<li>ROS2 Humble 설치(<a href="https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95">ROS2 Humble 설치 포스트 바로가기</a>)</li>
<li>turtlesim 관련 패키지 설치하기(<a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">turtlesim 패키지 설치 설명 포스트 바로가기</a>)</li>
<li>node 개념(<a href="https://velog.io/@i_robo_u/ROS2-Humble-Node-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-2">node 개념 설명 포스트 바로가기</a>)</li>
</ul>
<hr>
<h2 id="1-환경-설정하기">1. 환경 설정하기</h2>
<p>항상 그랬듯이 다음 명령어들로 /turtlesim과 /teleop_turtle을 실행해주자!</p>
<pre><code>ros2 run turtlesim turtlesim_node</code></pre><pre><code>ros2 run turtlesim turtle_teleop_key</code></pre><hr>
<h2 id="2-action-실습">2. Action 실습</h2>
<p><em><strong>/teleop_turtle</strong></em> node를 실행 했다면 터미널에 다음과 같은 메세지가 떴을 것이다. </p>
<pre><code>Use arrow keys to move the turtle.
Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. &#39;F&#39; to cancel a rotation.</code></pre><p>이때 두번째 줄에 있는 메세지는 action과 긴밀하게 연관 되어있다. (첫번째 방향키 움직이는 것은 topic과 관련 되어있으니 지금은 신경쓰지 않아도 된다.)</p>
<p>&quot;G|B|V|C|D|E|R|T&quot;는 각각 미리 설정된 방향을 의미한다. 예를 들어 &quot;E&quot; 같은 경우 turtle이 왼쪽 위를 바라보는 방향을 의미한다. </p>
<p>8개의 알파벳 중 하나를 누르면 /turtlesim node에 있는 action server에 goal을 보내는 것과 같다. 
/teleop_turlte node에서 즉 E를 누르면 turtle을 왼쪽 위를 보도록 돌리라는 goal을 action client가 /turtlesim node에 있는 action server에 보낸다.</p>
<p>그러면 action server는 turtle을 goal(e.g. 왼쪽 위)을 향하도록 돌리기 시작한다. 특정 방향에 도달하게 되면 다음과 같이 골에 도달 했다는 메세지를 출력하게 된다. </p>
<pre><code>[INFO] [turtlesim]: Rotation goal completed successfully</code></pre><p>&quot;F&quot; 키를 누르게 되면 goal을 향해 움직이다가 행위를 취소한다. </p>
<p>예를 들어 &quot;C&quot; 키를 누르고 goal에 도달하기 전에 &quot;F&quot;를 누르게 되면 /turtlesim node를 실행한 터미널에서는 다음과 같이 goal에 도달하기 위한 행위가 취소 됐다는 메세지를 출력할 것이다. </p>
<pre><code>[INFO] [turtlesim]: Rotation goal canceled</code></pre><p>action의 특징 중 하나는 action client(요청자), action server(제공자) 양측에서 행위를 취소할 수 있다는 것이다. 방금 위에서 &quot;F&quot; 키를 통해서 취소한 것은 action client 측에서 취소한 것에 해당한다. </p>
<p>action server에서 취소하는 행위는 &quot;abort&quot;라고 불리는데 이를 실습해보자. 
먼저 &quot;D&quot; key를 누르고 &quot;D&quot; goal에 도달하기 전에 &quot;G&quot; key를 빠르게 누른다. 
그러면 다음과 같이 abort되었다는 message를 볼 수 있을 것이다. </p>
<pre><code>[WARN] [turtlesim]: Rotation goal received before a previous goal finished. Aborting previous goal</code></pre><p>/turtlesim node 에 있는 action server같은 경우 새로운 goal이 들어올 경우 이전의 goal을 abort하게 되어 있다. 
그렇지만 이렇게 이전의 goal을 abort하는 것은 필수는 아니며 상황에 맞게 action server가 할 행동을 정의하면된다.(e.g. 현재 goal을 유지하며 새로운 goal을 abort하는 등)  </p>
<hr>
<h2 id="3-ros2-node-info">3. ros2 node info</h2>
<p>새로운 터미널을 열고 ros2 node info라는 명령어를 사용하게 되면 관심 node의 정보를 알 수 있다. </p>
<p>/turtlesim node에 대한 정보를 얻기 위해 다음을 새로운 터미널을 열어 입력하자. </p>
<pre><code>ros2 node info /turtlesim</code></pre><p>다음과 같이 subscriber, publisher, service, action server, action client에 대한 정보를 출력할 것이다. </p>
<pre><code>/turtlesim
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/color_sensor: turtlesim/msg/Color
    /turtle1/pose: turtlesim/msg/Pose
  Service Servers:
    /clear: std_srvs/srv/Empty
    /kill: turtlesim/srv/Kill
    /reset: std_srvs/srv/Empty
    /spawn: turtlesim/srv/Spawn
    /turtle1/set_pen: turtlesim/srv/SetPen
    /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute
    /turtle1/teleport_relative: turtlesim/srv/TeleportRelative
    /turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /turtlesim/get_parameters: rcl_interfaces/srv/GetParameters
    /turtlesim/list_parameters: rcl_interfaces/srv/ListParameters
    /turtlesim/set_parameters: rcl_interfaces/srv/SetParameters
    /turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Service Clients:

  Action Servers:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute
  Action Clients:</code></pre><p>Action Servers 부분을 살펴보면 /turtle1/rotate_absolute라는 action이 있는 것을 볼 수 있다. </p>
<p>이는 /turtlesim node는 /turtle1/rotate_absolute라는 action을 제공한다는 것을 의미한다. </p>
<p>반면에, /teleop_turtle의 node info를 살펴보면 다음과 같다. </p>
<p>먼저 ros2 node info를 터미널에 입력하자. </p>
<pre><code>ros2 node info /teleop_turtle</code></pre><p>그러면 다음과 같은 메세지가 출력 될 것이다. </p>
<pre><code>/teleop_turtle
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Service Servers:
    /teleop_turtle/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /teleop_turtle/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /teleop_turtle/get_parameters: rcl_interfaces/srv/GetParameters
    /teleop_turtle/list_parameters: rcl_interfaces/srv/ListParameters
    /teleop_turtle/set_parameters: rcl_interfaces/srv/SetParameters
    /teleop_turtle/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Service Clients:

  Action Servers:

  Action Clients:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute</code></pre><p>Action Clients쪽을 보면 /turtle1/rotate_absolute가 있는 것을 볼 수 있고, 이는 /turtle1/rotate_absolute로 action goal을 보낸 다는 것을 의미한다. </p>
<hr>
<h2 id="4-ros2-action-list">4. ros2 action list</h2>
<p>다음으로 살펴 볼 명령어는 ros2 action list이다. 
해당 명령어는 현재 ROS graph에 존재하는 모든 action을 알려준다. 
백문이 불여일견! 바로 실행해보자.</p>
<pre><code>ros2 action list</code></pre><p>다음과 같은 action을 확인 할 수 있다. </p>
<pre><code>/turtle1/rotate_absolute</code></pre><p>즉, 현재 존재하는 action은 /turtle1/rotate_absolute임을 의미한다. 
이 action은 앞서 2. action 실습에서 살펴봤던 것처럼 turtle을 특정 방향으로 돌리는 행위를 하는 action이다. </p>
<hr>
<h3 id="41-ros2-action-list--t">4.1 ros2 action list -t</h3>
<p>action list 명령어와 함께 사용 할 수 있는 인자가 있는데 &quot;-t&quot; 이다. </p>
<p>이는 현존하는 action의 type을 같이 표시해준다. </p>
<pre><code>ros2 action list -t</code></pre><p>을 입력하면 다음과 같이 type이 표시된다. </p>
<pre><code>/turtle1/rotate_absolute [turtlesim/action/RotateAbsolute]</code></pre><p>이는 /turtle1/rotate_absolute라는 action은 turtlesim/action/RotateAbsolute라는 type을 사용한다는 의미이다. 
이 type은 명령어를 통해 action을 요청하거나 code에서 action을 요청하고자 할 때 알아야 된다. 나중에 필요하면 실습할 예정이니 지금은 그런게 있구나 정도로 생각하고 넘어가면 된다. </p>
<hr>
<h1 id="5-ros2-action-info">5. ros2 action info</h1>
<p>다음으로 살펴볼 명령어는 ros2 action info이다. 
해당 명령어를 사용하면 관심 action에 대한 요약된 정보를 알 수 있다.</p>
<p>거두절미하고 실습해보자. 다음 명령어를 터미널에 복붙!</p>
<pre><code>ros2 action info /turtle1/rotate_absolute</code></pre><p>이때 /turtle1/rotate_absolute는 action 이름인 것에 주목하자!</p>
<p>그러면 다음과 같은 메세지를 출력할 것이다. </p>
<pre><code>Action: /turtle1/rotate_absolute
Action clients: 1
    /teleop_turtle
Action servers: 1
    /turtlesim</code></pre><p>요약하자면 Action은 /turtle1/rotate_absolute 이고 해당 action의 client와 server 수는 각각 1개씩 그리고 해당 client와 server가 있는 node는 /teleop_turtle, /turtlesim node이다. </p>
<hr>
<h1 id="6-ros2-interface-show">6. ros2 interface show</h1>
<p>다음으로 살펴볼 명령어는 ros2 interface show이다. 
action을 command나 code를 통해 요청하기 전에 정보가 더 필요한데, 이를 알아낼 수 있는 명령어는 ros2 interface show이다. </p>
<p>이 명령어는 action type의 구조를 알려준다. </p>
<p>예를 들어 /turtle1/rotate_absolute의 action type을 다음과 같이 interface show를 통해 알아보면,</p>
<pre><code>ros2 interface show turtlesim/action/RotateAbsolute</code></pre><p>다음과 같이 type 구조를 담고 있는 메세지를 보여준다. </p>
<pre><code># The desired heading in radians
float32 theta
---
# The angular displacement in radians to the starting position
float32 delta
---
# The remaining rotation in radians
float32 remaining</code></pre><p>살펴보자면 float32 theta는 goal 방향을 나타낸다. 
float32 delta는 시작시점의 방향과 goal 방향까지 차이를 나타낸다. 
foat32 remaining은 현재시점의 방향과 goal 방향까지 차이를 나타낸다. feedback으로 사용될 것이다. </p>
<p>그럼 이 구조가 어떻게 사용되는지 다음 명령어를 통해서 살펴보자. </p>
<hr>
<h1 id="7-ros2-action-send_goal">7. ros2 action send_goal</h1>
<p>이제 action 이름, type, 구조를 알아냈으니 직접 명령어를 통해서 action을 요청해볼 차례다. </p>
<p>기본적인 문법 구조는 다음과 같다. </p>
<pre><code>ros2 action send_goal &lt;action 이름&gt; &lt;action type&gt; &lt;action 구조에 맞는 값&gt;</code></pre><p>이때 &lt;action 구조에 맞는 값&gt; 같은 경우에는 YAML format으로 작성되야한다. 뭔지 모르겠지만 실습을 통해 익히면 &#39;아 이렇게 생겼구나&#39; 할 것이니 걱정 안해도 된다. </p>
<p>여태 우리가 살펴왔던 /turtle1/rotate_absolute action에 대한 명령어를 구성해보면 다음과 같다. </p>
<pre><code>ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute &quot;{theta: 1.57}&quot;</code></pre><p>명령어를 살펴보자면, &quot;/turtle1/rotate_absolute의 이름을 갖는 action은 turtlesim/action/RoateAbsolute라는 type을 갖고 이때 goal 방향 theta는 1.57 radian으로 하겠다&quot; 라는 의미이다. </p>
<p>이 명령어를 입력하게 되면 turtle이 회전하기 시작하면서 다음과 같은 메세지를 출력할 것이다. </p>
<pre><code>Waiting for an action server to become available...
Sending goal:
   theta: 1.57

Goal accepted with ID: f8db8f44410849eaa93d3feb747dd444

Result:
  delta: -1.568000316619873

Goal finished with status: SUCCEEDED</code></pre><p>찬찬히 살펴보자면 action server가 이용가능 한지 대기한 후, goal theta 1.57를 보낸다. 
goal은 고유한 ID가 부여된다. (e.g. 여기선 f8db~ 어쩌구 로 부여됐다) 
이때 Result delta는 시작시점 방향과 목표 방향간의 차이를 radian으로 나타내고 있다. 
그리고 마지막에 goal에 성공적으로 달성했음을 말해주고 있다. </p>
<p>추가적으로 goal방향에 도달하는 동안 feedback을 보고 싶으면 다음과 같이 명령어에 --feedback이라는 인자를 추가해주면 된다. </p>
<pre><code>ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute &quot;{theta: -1.57}&quot; --feedback</code></pre><p>그러면 다음과 같이 Feedback을 출력할 것이다. </p>
<pre><code>Sending goal:
   theta: -1.57

Goal accepted with ID: e6092c831f994afda92f0086f220da27

Feedback:
  remaining: -3.1268222332000732

Feedback:
  remaining: -3.1108222007751465

…

Result:
  delta: 3.1200008392333984

Goal finished with status: SUCCEEDED</code></pre><p>이 feedback은 goal 방향까지 남은 각도를 의미하며, 이는 goal 방향에 도달할 때 까지 출력 될 것이다. </p>
<hr>
<p>여태까지 ros2에 통신 방법 중 하나인 action에 대해 배웠다. 
action이 service와 topic으로 이루어졌기 때문에 엄밀히 말하면 service와 topic로 action 기능과 유사하게 구현 할 수 있다. </p>
<p>그렇지만 있는 것을 편하게 가져다사용하면 통신 방법을 구현하는데 실수를 줄 일 수 있으므로 필요하면 쓰도록 하자! </p>
<p>action은 업무를 할 때 엄청 자주 쓰이진 않는 것 같은게 개인적인 경험이다. 그렇지만 다소 특수한 상황에서 유용하게 사용될 수 있을 것 같다. 
대표적인 예시가 Navigation stack에서 move_base이다. (이는 추후에 필요하면 다루도록 하겠다.)</p>
<p>오늘도 여기까지 온 스스로를 칭찬해주도록 하자!</p>
<p>토닥토닥</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a></p>
<p>참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Actions/Understanding-ROS2-Actions.html">https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Actions/Understanding-ROS2-Actions.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/bbd2b45b-b55e-43d9-b5c9-e74c5d65591c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble Parameter 이해하기, ROS2 명령어-5]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Parameter-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-5</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Parameter-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-5</guid>
            <pubDate>Sun, 20 Aug 2023 12:36:08 GMT</pubDate>
            <description><![CDATA[<p>ros2로 개발하다보면 parameter라는 것을 많이 설정하게 될 것이다. </p>
<p>이 parameter가 무엇인지 오늘 배워보도록 하자!</p>
<hr>
<p><strong>목표</strong>
ROS2에서 parameter 값을 확인하고, 설정하고, 저장하고, 불러오는 방법에 대해서 배운다. </p>
<p><strong>예상 소요시간</strong>
5분</p>
<p><strong>영상 튜토리얼</strong>
추후 업데이트</p>
<hr>
<h2 id="배경지식">배경지식</h2>
<p>parameter는 간단히 말해 node의 설정 값이라고 생각하면 된다.</p>
<p>예를 들어 음식을 주문 하는 node가 있다고 하면 한번에 최대 주문 가능한 음식 개수, 주문할 전화번호 등이 node에서 설정이 필요한 값들이라고 할 수 있는데, 이들이 parameter이다. </p>
<p>이 설정 값들은 interger, float, boolean, string, list 같은 형식을 갖는다. </p>
<hr>
<h2 id="준비물">준비물</h2>
<ul>
<li>ROS2 Humble 설치(<a href="https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95">ROS2 Humble 설치 포스트 바로가기</a>)</li>
<li>turtlesim 관련 패키지 설치하기(<a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">turtlesim 패키지 설치 설명 포스트 바로가기</a>)</li>
</ul>
<hr>
<h3 id="1-환경-설정하기">1. 환경 설정하기</h3>
<p>항상 그랬듯이 다음 명령어들로 /turtlesim과 /teleop_turtle을 실행해주자!</p>
<pre><code>ros2 run turtlesim turtlesim_node</code></pre><pre><code>ros2 run turtlesim turtle_teleop_key</code></pre><hr>
<h3 id="2-ros2-param-list">2. ros2 param list</h3>
<p>이제 본격적으로 parameter 관련 명령어에 대해 알아볼 건데 </p>
<p>제일 처음 명령어는 ros2 param list이다. </p>
<p>해당 명령어는 다음과 같고 이를 실행해보자!</p>
<pre><code>ros2 param list</code></pre><p>그렇다면 현재 /teleop_turtle과 /turtlesim node의 pamrameter들이 다음과 같이 각각 나열 될 것이다. </p>
<pre><code>/teleop_turtle:
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  scale_angular
  scale_linear
  use_sim_time
/turtlesim:
  background_b
  background_g
  background_r
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  use_sim_time</code></pre><p>상단의 결과를 보면 각 node이름 별로 구분되게 parameter가 출력됐고(node별로 각각 다른 설정값을 갖기 때문) 공통적으로 use_sim_time이라는 parameter가 있는 것을 관찰 할 수 있다. </p>
<p>몇가지 parameter를 살펴보면, /turtlesim node에 있는 background_b는 왠지 RGB 색상중 blue계열의 설정 값과 관련되있는 것 같다! </p>
<p>그럼 이 값을 살펴볼 수 있는 다음 명령어를 배워보자</p>
<hr>
<h3 id="3-ros2-param-get">3. ros2 param get</h3>
<p>ros2 param get은 특정 parameter의 현재 설정 값을 보여주는 명령어이다. </p>
<p>문법은 다음과 같다. </p>
<pre><code>ros2 param get &lt;관심 노드 이름&gt; &lt;알고자하는 parameter 이름&gt;</code></pre><p>즉 관심 노드의 이름과 알고 싶어하는 parameter 이름으로 명령어를 구성해주면 그 설정값을 반환해준다. </p>
<p>다음 명령어를 통해 실습해보자. </p>
<pre><code>ros2 param get /turtlesim background_g</code></pre><p>위 명령어 뜻은 /turtlesim node에서 background_g라는 parameter의 설정값을 get(보여줘)해줘 라는 의미이다. </p>
<p>명령어를 실행하게 되면 다음과 같은 값을 반환 할 것이다. </p>
<pre><code>Integer value is: 86</code></pre><p>이 말은 현재 background_g는 interger type이며 그 값은 86이라는 의미가 된다. </p>
<p>추가로 background_r과 backgound_b에 대해 살펴보는 것은 여러분에게 맡기겠다!</p>
<p>다음은 이 설정 값들을 원하는 값으로 세팅하는 방법에 대해 배워보겠다. </p>
<hr>
<h3 id="4-ros2-param-set">4. ros2 param set</h3>
<p>다음과 같은 명령어를 사용하면 원하는 parameter를 설정할 수 있다. </p>
<pre><code>ros2 param set &lt;원하는 node 이름&gt; &lt;바꾸고자 하는 parameter 이름&gt; &lt;설정하고자하는 값&gt;</code></pre><p>위의 명령어는 원하는 node에 있는 parameter 이름의 값을 &#39;설정하고자하는 값&#39;으로 세팅 해줘 라는 말이다. </p>
<p>다음 명령어를 통해 실습해보자. </p>
<pre><code>ros2 param set /turtlesim background_r 150</code></pre><p>이는 /turtlesim node에 있는 background_r이라는 parameter를 150이라는 값으로 설정하겠어 라는 의미이다. </p>
<p>이 명령어를 실행했을 때 다음과 같은 메세지가 출력되면 성공!</p>
<p>박수~!! 짝짝짝</p>
<pre><code>Set parameter successful</code></pre><p>Turtlesim창을 열어서 확인해보면 색이 바뀐 것을 확인 할 수 있을 것이다. </p>
<p>참고로 이렇게 바뀐 node의 설정 값들은 일시적이고 node를 껐다 키면 다시 원래대로 돌아온다. </p>
<p>그럼 이렇게 바꾼 parameter값을 저장하고 싶으면 어떻게 할까? </p>
<p>다행히도 ros2개발자는 이 상황을 고려하여 이미 명령어를 개발해놨다. ㅎㅎ</p>
<hr>
<h3 id="5-ros2-param-dump">5. ros2 param dump</h3>
<p>ros2 param dump는 특정 node가 갖는 parameter의 설정 값을 보여주고 필요하면 저장할 수 있는 기능을 제공한다. </p>
<p>기본 문법은 다음과 같다. </p>
<pre><code>ros2 param dump &lt;node 이름&gt;</code></pre><p>이는 &#39;node 이름&#39;에 있는 parameter를 보여줘 라는 의미다. </p>
<p>이 node의 parameter를 저장하고 싶으면 다음과 같은 명령어를 사용한다. </p>
<pre><code>ros2 param dump &lt;node 이름&gt; &lt;parameter 파일이름.yaml&gt;</code></pre><p>위의 명령어는 &#39;node 이름&#39;에 있는 parameter를 &#39;parameter 파일이름.yaml&#39;의 형태로 현재 경로에 저장해준다. </p>
<p>그럼 다음 명령어로 실습해보자. </p>
<pre><code>ros2 param dump /turtlesim &gt; turtlesim.yaml</code></pre><p>이 말은 즉, /turtlesim에 있는 parameter를 turtlesim.yaml파일에 저장해줘~ 라는 의미이다. </p>
<p>ubuntu 명령어 ls을 사용하면 현재 경로에 turtlesim.yaml파일이 생성됐음을 확인 할 수 있다. </p>
<p>그럼 이 파일을 load하려면 어떻게 할까?</p>
<hr>
<h3 id="6-ros2-param-load">6. ros2 param load</h3>
<p>이번에 배워볼 명령어는 방금 저장한 parameter를 불러오는 명령어이다. </p>
<p>기본 문법은 다음과 같다. </p>
<pre><code>ros2 param load &lt;node 이름&gt; &lt;불러오고자하는 parameter file경로 또는 이름&gt;</code></pre><p>이는 &#39;node 이름&#39;을 갖는 node에 &#39;불러오고자하는 parameter file경로 또는 이름&#39;의 파일에 있는 설정값들을 불러와줘 라는 의미이다. </p>
<p>다음 명령어로 실습해보자. </p>
<pre><code>ros2 param load /turtlesim turtlesim.yaml</code></pre><p>간단히 의미를 살펴보자면 /turtlesim node에 turtlesim.yaml에 있는 설정값들을 불러와줘 라는 의미이다. </p>
<p>명령어를 실행하면 다음과 같은 메세지가 뜰 것이다.</p>
<pre><code>Set parameter background_b successful
Set parameter background_g successful
Set parameter background_r successful
Set parameter qos_overrides./parameter_events.publisher.depth failed: parameter &#39;qos_overrides./parameter_events.publisher.depth&#39; cannot be set because it is read-only
Set parameter qos_overrides./parameter_events.publisher.durability failed: parameter &#39;qos_overrides./parameter_events.publisher.durability&#39; cannot be set because it is read-only
Set parameter qos_overrides./parameter_events.publisher.history failed: parameter &#39;qos_overrides./parameter_events.publisher.history&#39; cannot be set because it is read-only
Set parameter qos_overrides./parameter_events.publisher.reliability failed: parameter &#39;qos_overrides./parameter_events.publisher.reliability&#39; cannot be set because it is read-only
Set parameter use_sim_time successful</code></pre><p>대략 성공했다는 메세지인 것 같다. 그렇지만 Read-only parameter라서 실패했다는 메세지도 있는데 이는 일부 parameter들은 node가 실행될 때 설정된 값에서 바꿀 수 없다는 의미이다. </p>
<p>그렇다면 node가 실행될 때 parameter 값을 설정하고 싶으면 어떻게 할까?</p>
<p>걱정하지 마시라. 다음 명령어에서 배워볼 것이다. </p>
<hr>
<h3 id="7-load-parameter-file-on-node-startup">7. Load parameter file on node startup</h3>
<p>ros2 run이라는 명령어는 node를 실행하는데 사용됐다. </p>
<p>여기를 약간 변형하면 node를 실행할 때 원하는 parameter로 설정할 수 있다. </p>
<p>기본 문법은 다음과 같다. </p>
<pre><code>ros2 run &lt;package 이름&gt; &lt;executable 이름&gt; --ros-args --params-file &lt;parameter 파일 이름&gt;</code></pre><p>명령어 구성을 살펴보면 &#39;package 이름&#39;의 package에 &#39;executable 이름&#39;을 실행할 때 parmeter file은 &#39;parameter 파일 이름&#39;이고 이 안에 있는 설정값들로 node의 parameter값을 세팅 해줘 라는 의미이다. </p>
<p>다음 명령어로 간단한 예제를 살펴보자. </p>
<pre><code>ros2 run turtlesim turtlesim_node --ros-args --params-file turtlesim.yaml</code></pre><p>이는, turtlesim패키지에 turtlesim_node라는 executable을 실행할 건데 parameter값들이 들어있는 parameter파일을 turtlesim.yaml이고 여기에 있는 값들로 node의 parameter들을 설정해줘 라는 말이 된다. </p>
<p>정상적으로 실행 됐다면 배경화면이 바뀐 TurtleSim 화면을 볼 수 있을 것이다. </p>
<hr>
<p>여때까지 node의 parameter의 개념과 이를 확인하고, 설정하고, 저장하고, 불러오는 명령어에 대해서 배웠다. </p>
<p>경험상 오늘 배운 명령어들은 파라미터가 정상적으로 node에 설정되어있나? 확일 할 때 자주 사용하는 것 같다.</p>
<p>즉 개발하다보면 자연스럽게 익숙해지는 부분이고 까먹었으면 인터넷에서 찾아보면 된다. 나도 종종 문법 까먹으면 찾아본다. ㅎㅎ</p>
<p>오늘도 역시 스스로를 칭찬해주자~! </p>
<p>&quot;크으 오늘도 멋진 나ㅎㅎ&quot;</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p><strong>문의메일</strong>: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
<strong>오픈톡 문의</strong>: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
<strong>IRoboU 유튜브 채널</strong>: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a></p>
<p><strong>참고 문헌</strong>: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Parameters/Understanding-ROS2-Parameters.html">https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Parameters/Understanding-ROS2-Parameters.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/aae213c1-d238-49ec-8d08-8829575b8d13/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble Service 이해하기, ROS2 명령어-4]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Service-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-4</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Service-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-4</guid>
            <pubDate>Sat, 19 Aug 2023 08:31:11 GMT</pubDate>
            <description><![CDATA[<p>ros topic 다음으로 자주 쓰이는 개념이 service이다. </p>
<p>거두절미 하고 바로 service에 대해 배워보자</p>
<p>Let&#39;s go</p>
<hr>
<p><strong>목표</strong>
ROS2 Service의 개념에 대해서 배운다!</p>
<p><strong>예상 소요시간</strong>
10분</p>
<hr>
<h2 id="배경지식">배경지식</h2>
<p>Service란 무엇일까? </p>
<p>이름에서 한 번 유추해보자. 1분의 시간을 주겠다. </p>
<p>Service란 이름대로 무엇가를 요청하는 것하는 것을 의미한다. 보통 요청했으면 어떤 반응을 얻지 않는가?</p>
<p>예를 들어 식당에 전화해 음식 배달 서비스를 요청했다고 해보자. 그러면 요청한 서비스가 접수되고 음식 서비스가 가능한지 여부를 상대방에게서 듣게된다. </p>
<p>이렇듯 서비스는 요청(이하 call) 그리고 응답(response)형태로 이루어진 node간 통신 방식을 의미한다. </p>
<p>하단의 그림 2개를 살펴보자 </p>
<p><strong>&lt;그림1&gt;</strong></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/badebf01-7af3-44a4-a896-aae50f5280d3/image.png" alt=""></p>
<p>그림 1에서 보면 service client(고객이라고 생각하면 쉽다)는 요청사항을 request message를 생성해 service server(서비스를 세공하는 식당이라고 생각하자)에 요청(request)한다. 그러면 server는 이를 받아 처리한 후 고객이 요청한 서비스가 어떻게 처리 됐는지 응답(response) 형태로 고객에게 알려준다. </p>
<p><strong>&lt;그림2&gt;</strong>
<img src="https://velog.velcdn.com/images/i_robo_u/post/f5b420b5-bf3f-4227-8541-bd5a45494b96/image.png" alt=""></p>
<p>그림 2는 2명의 서로 다른 고객(2개의 node)이 같은 형태의 서비스를 service server(식당)에 요청하고 있다. </p>
<hr>
<h2 id="준비물">준비물</h2>
<ul>
<li><p>terminator 설치</p>
<ul>
<li>terminator는 기존 터미널과 같은 역할을 하지만 추가적으로 사용자가 이용할 때 편리한 기능을 제공한다. 그래서 ROS2로 개발할 때 설치해두면 훨씬 편하니 하나 설치해두도록하자. 설치방법은 밑에 명령어 실행하면 끝!    <pre><code>sudo apt-get install terminator</code></pre></li>
</ul>
</li>
<li><p>ROS2 Humble 설치(<a href="https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95">ROS2 Humble 설치 포스트 바로가기</a>)</p>
</li>
<li><p>turtlesim 관련 패키지 설치하기(<a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">turtlesim 패키지 설치 설명 포스트 바로가기</a>)</p>
</li>
<li><p>node 개념 (<a href="https://velog.io/@i_robo_u/ROS2-Humble-Node-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-2">node 개념 설명 포스트 바로가기</a>)</p>
</li>
<li><p>topic 개념(<a href="https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Topic-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-3">topic 개념 설명 포스트 바로가기</a>)</p>
</li>
</ul>
<hr>
<h2 id="본론">본론</h2>
<h3 id="1-기본-세팅">1. 기본 세팅</h3>
<p>/turtlesim 과 /teleop_turtle node를 다음 명령어를 통해 각각 실행한다. </p>
<pre><code>ros2 run turtlesim turtlesim_node</code></pre><pre><code>ros2 run turtlesim turtle_teleop_key</code></pre><h3 id="2-ros2-service-list-명령어">2. ros2 service list 명령어</h3>
<p>명령어 이름에서도 짐작할 수 있듯이 ros2 servie list라는 명령어는 현재 실행된 node에 의해 제공되는 모든 사용가능한 service를 나열해준다. </p>
<p>다음 명령어를 실행해보자.</p>
<pre><code>ros2 service list</code></pre><p>그렇다면 /turtlesim과 /teleop_turtle node에 의해 제공되는 service들의 이름이 다음과 같이 나열될 것이다. </p>
<pre><code>/clear
/kill
/reset
/spawn
/teleop_turtle/describe_parameters
/teleop_turtle/get_parameter_types
/teleop_turtle/get_parameters
/teleop_turtle/list_parameters
/teleop_turtle/set_parameters
/teleop_turtle/set_parameters_atomically
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/describe_parameters
/turtlesim/get_parameter_types
/turtlesim/get_parameters
/turtlesim/list_parameters
/turtlesim/set_parameters
/turtlesim/set_parameters_atomically</code></pre><p>아... 보기만 해도 뭐가 뭔지 어질어질 하네..</p>
<p>그렇지만 I Robo U와 함께라면 걱정할 필요가 없다. </p>
<p>&quot;turtlesim 관련 패키지 설치하기(<a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">turtlesim 패키지 설치 설명 포스트 바로가기</a>)&quot; 글을 본 사람들은 /spawn이나 /turtle1/set_pen과 같은 service를 몇 번 사용해본 경험이 있을 것이다. </p>
<p>없다고? 괜찮다. 차근차근 살펴볼 것이다. </p>
<hr>
<h3 id="3-ros2-service-type-명령어">3. ros2 service type 명령어</h3>
<p>우리가 살면서 서비스를 요청할 때 요구하는 사항이 뭔지 잘 정리해서 요구하듯이 ROS Service에도 내가 무엇을 요청하려는지 잘 정리해서 보내야한다. 이렇게 정해진 형태를 service type이라고 한다. </p>
<p>예를 들어 음식 주문 서비스를 이용할 때는 기본적으로 주문할 음식 이름, 배달 장소 등을 직원에게 전달해야 한다. </p>
<p>그럼 이런 service별로 정의된 type을 어떻게 알 수 있을까?</p>
<p>ros2 service type을 사용하면 되는데 문법은 다음과 같다. </p>
<pre><code>ros2 service type &lt;서비스 이름&gt;</code></pre><p>예를 들어 /clear 라는 서비스의 type을 알아보자</p>
<p>다음 명령어를 실행하자. </p>
<pre><code>ros2 service type /clear</code></pre><p>다음과 같은 내용이 반환될 것이다. </p>
<pre><code>std_srvs/srv/Empty</code></pre><p>이말은 std_srvs라는 패키지에 srv라는 폴더안에 Empty라는 service type을 사용한다는 말이다. </p>
<p>Empty는 요청할 때 아무 데이터도 채워 넣지 않고 그냥 요청하면 될 때 사용한다. 또한 응답 데이터도 없다. </p>
<p>이게 뭔 말인가 싶을것이다. 나도 뭔지 처음에는 이해하기 어려웠다. </p>
<p>학습하다보면 점점 익숙해지므로 지금은 그러려니 하고 넘어가면 된다.</p>
<hr>
<h4 id="31-ros2-servie-list--t-명령어">3.1. ros2 servie list -t 명령어</h4>
<p>ros2 service list 명령어와 같이 사용할 수 있는 -t 라는 인자가 있다.</p>
<p>이는 type을 의미하는데 service의 이름들과 해당 service의 type들도 같이 표시해준다. </p>
<p>다음 명령어를 입력해보자. </p>
<pre><code>ros2 service list -t</code></pre><p>다음과 같은 화면이 뜰 것이다. </p>
<pre><code>/clear [std_srvs/srv/Empty]
/kill [turtlesim/srv/Kill]
/reset [std_srvs/srv/Empty]
/spawn [turtlesim/srv/Spawn]
...
/turtle1/set_pen [turtlesim/srv/SetPen]
/turtle1/teleport_absolute [turtlesim/srv/TeleportAbsolute]
/turtle1/teleport_relative [turtlesim/srv/TeleportRelative]
...</code></pre><hr>
<h3 id="4-ros2-service-find-명령어">4. ros2 service find 명령어</h3>
<p>또 다른 유용한 명령어는 ros2 service find인데, 이 명령어를 사용하면 이 특정 service type을 갖는 service들을 찾아준다. </p>
<p>다음 명령어는 std_srvs패키지 안에 srv 폴더에 있는 Empty와 같은 type을 사용하는 service를 찾아줘~ 라는 의미이다. </p>
<pre><code>ros2 service find std_srvs/srv/Empty</code></pre><p>다음을 반환 할 것이다. </p>
<pre><code>/clear
/reset</code></pre><p>즉 /clear와 /reset service는 Empty라는 service type을 사용한다. </p>
<hr>
<h3 id="5-ros2-interface-show-명령어">5. ros2 interface show 명령어</h3>
<p>다음으로 살펴볼 명령어는 ros2 interface show라는 명령어이다. </p>
<p>이 명령어를 사용하면 특정 service type의 구조(request와 response정보)를 보여준다(show).</p>
<p>아까 살펴봤던 Empty의 구조를 살펴보자. </p>
<p>다음 명령어를 입력하면, </p>
<pre><code>ros2 interface show std_srvs/srv/Empty</code></pre><p>다음과 같이 화면에 뜬다.</p>
<pre><code>---</code></pre><p>오잉? 작은 막대기 3개가 나타나고 끝났다. </p>
<p>오타아니야? 라고 생각할 수 있겠지만 이게 정상이다. </p>
<p>이 작은 막대기 3개는 request와 response를 구분 짓는 구분선이다. </p>
<p>구분선 위로는 request의 필요한 구조가, 구분선 밑으로는 response의 필요한 구조가 표시된다. </p>
<p>Empty type의 경우 request와 response는 아무 것도 필요하지 않음을 의미한다. </p>
<p>그렇다면 다른 type을 살펴보자. </p>
<p>turtlesim패키지 안에 있는 srv폴더안 Spawn이라는 service type을 살펴보기 위해 다음 명령어를 실행하자. </p>
<pre><code>ros2 interface show turtlesim/srv/Spawn</code></pre><p>다음과 같은 내용이 출력될 것이다. </p>
<pre><code>float32 x
float32 y
float32 theta
string name # Optional.  A unique name will be created and returned if this is empty
---
string name</code></pre><p>천천히 살펴보면 구분선 위 request부분에는 float32 x, float32 y, float32 theta, string name 이렇게 있는데 float32와 string은 데이터 타입을 의미하고, x,y,theta는 turtle이 생성될 위치와 각도, 그리고 name은 생성될 turtle의 이름을 의미한다. </p>
<p>구분 선 아래 response부분에는 string name이 있는데 생성할 때 지정했던 이름이 반환된다고 보면 된다. 이게 있는 이유는 request할 때 이름을 특정하게 지정하지 않으면 고유한 이름을 지어주는데 그 고유한 이름을 사용자가 response형태로 받아볼 수 있게 하기 위함이다.</p>
<hr>
<h3 id="6-ros2-service-call-명령어">6. ros2 service call 명령어</h3>
<p>지금 까지 서비스 이름은 무엇이고 서비스는 어떻게 구성되어있는지 알려주는 명령어를 배웠는데, </p>
<p>그 이유는 ros2 service call이라는 명령어를 사용하기 위한 빌드업이였다!</p>
<p>ros2 service call은 터미널에서 원하는 서비스를 요청하는데 사용할 수 있는 명령어이다. </p>
<p>문법은 다음과 같다. </p>
<pre><code>ros2 service call &lt;요청하고자 하는 서비스 이름&gt; &lt;해당 서비스의 타입&gt; &lt;서비스 타입의 request 구조&gt;</code></pre><p>예를 들어 /clear 서비스를 요청하려면 다음과 같은 명령어를 사용할 수 있다. </p>
<pre><code>ros2 service call /clear std_srvs/srv/Empty</code></pre><p>위의 명령어는 /clear라는 서비스중 std_srvs 패키지의 srv 폴더에 있는 Empty라는 타입을 사용하는 서비스를 불러줘. </p>
<p>근데 Empty는 앞서 살펴봤듯이 아무 것도 작성할 필요가 없으니까 서비스 타입의 request 구조란은 비워 놓겠어. </p>
<p>라는 의미이다. </p>
<p>turtle을 조금 움직인 후 해당 명렁어를 입력하면 다음과 같이 흰색으로 표시됐던 지나온 경로가 모두 지워지는 것을 확인 할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/18530190-4545-4084-b7f6-b50fd90a9d61/image.png" alt=""></p>
<p>또 다른 예시로 spawn을 사용해보자. </p>
<p>사용하는 명령어는 다음과 같다. </p>
<pre><code>ros2 service call /spawn turtlesim/srv/Spawn &quot;{x: 2, y: 2, theta: 0.2, name: &#39;&#39;}&quot;</code></pre><p>하나씩 뜯어보면 /spawn이라는 서비스 중에 turtlesim 패키지의 srv폴더 안에 있는 Spawn이라는 서비스 타입을 사용하는 녀석을 요청할거야. 근데 요청할 때 x는 2, y는 2 theta는 0.2로 부탁해. </p>
<p>이름은 따로 적지 않을게~</p>
<p>라는 의미를 갖는다. </p>
<p>이 명령어를 터미널에서 실행하면 아래와 유사하게 turtle하나가 생성될 것이다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/108188c9-b352-457d-9123-872b6e83a886/image.png" alt=""></p>
<hr>
<p>여태까지 서비스의 개념부터 관련된 명령어 도구들을 엄청 배웠다. </p>
<p>ROS2로 인공지능 &amp; 로봇 개발을 하다보면 디버깅 할 때나 실험할 때 자주 사용될 수 있는 명령어들이라서 지금 익숙하지 않더라도 나중에 쓰다보면 익숙해질 것이다. </p>
<p>까먹어도 인터넷에서 다시 찾아서 하면 되니 걱정안해도 된다. </p>
<p>여기까지 왔으니까 스스로에게 칭찬 한 마디 해주자. </p>
<p>&quot;넌 역시 대단해 ㅎㅎ&quot;</p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!</p>
<p><strong>문의메일</strong>: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
<strong>오픈톡 문의</strong>: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
<strong>IRoboU 유튜브 채널</strong>: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a></p>
<p><strong>참고 문헌</strong>: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Services/Understanding-ROS2-Services.html">https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Services/Understanding-ROS2-Services.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/04fc3cbe-a74e-4dc9-8714-8be43ae1255b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble Topic 이해하기, ROS2 명령어-3]]></title>
            <link>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Topic-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-3</link>
            <guid>https://velog.io/@i_robo_u/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-ROS2-Humble-Topic-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-3</guid>
            <pubDate>Fri, 18 Aug 2023 01:38:57 GMT</pubDate>
            <description><![CDATA[<p>아마 ROS2를 사용해서 인공지능/로봇을 개발하다보면 가장 많이 접하는 용어중에 하나가 topic, node일 것이다. </p>
<p>이번 글에서는 그 중 하나인 topic 개념에 대해서 배워볼 예정이다.</p>
<p>준비되었는가? </p>
<p>그럼 Let&#39;s go!!</p>
<hr>
<p><strong>목표</strong> </p>
<ul>
<li>rqt_graph 명령어 도구를 통해 ROS2 topic개념을 배운다.</li>
</ul>
<p><strong>대상</strong></p>
<ul>
<li>입문</li>
</ul>
<p><strong>예상 소요시간</strong>
약 20분</p>
<hr>
<h2 id="배경지식">배경지식</h2>
<p>로봇 내부에서는 수많은 데이터와 신호들이 오가는데, 이때 이 정보들을 message로 만들어 topic이라는 공간을 통해 message를 발행하고 구독한다. 
사람을 예로 들면 &quot;이 공간(Topic)에서는 이 message로만 이야기 할 수 있어&quot; 로 이해하면 쉽다. </p>
<p>저번 시간에 배운 node는 하단의 그림 처럼 여러 topic에 message를 발행하거나 여러 topic을 구독할 수 있다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/531d74ac-8182-4d72-a984-cf5930067ce5/image.png" alt=""></p>
<p>이렇듯 topic를 이용하면 node간 데이터 교환이 정말 수월하기 때문에 로봇개발시 자주 사용된다. </p>
<hr>
<h2 id="준비물">준비물</h2>
<p>이전 포스트를 통해 node라는 개념을 익혔을 것! 
<a href="https://velog.io/@i_robo_u/ROS2-Humble-Node-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-2">개발자와 함께하는 ROS2 Humble Node 이해하기, ROS2 명령어-2 바로가기</a></p>
<hr>
<h3 id="1-환경-세팅하기">1. 환경 세팅하기</h3>
<p>먼저 turtlesim을 실행하도록 하자</p>
<pre><code>ros2 run turtlesim turtlesim_node</code></pre><p>터미널을 하나 더 열어 teleop_key도 같이 실행해주자</p>
<pre><code>ros2 run turtlesim turtle_teleop_key</code></pre><hr>
<h3 id="2-rqt_graph">2. rqt_graph</h3>
<p>이제 rqt_graph라는 것을 실행해보자. </p>
<p>rqt_graph는 간단히 말해 node와 topic간에 연결 상태를 시각화해주는 툴이라 생각하면 되겠다. </p>
<p>실행하는 방법은 다음 명령어를 터미널에 입력한다. </p>
<pre><code>rqt_graph</code></pre><p>그럼 다음과 같은 창이 보일 것이다. 
*참고로 rqt를 실행해 Plugins -&gt; Instropection -&gt; Node Graph를 선택해도 rqt_graph를 실행한 것과 같은 효과를 볼 것이다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/f5dea136-2159-4f36-b05a-82ecc7bdff80/image.png" alt=""></p>
<p>이 이미지에는 /teleop_turtle, /turtlesim이라는 node와 /turtle/cmd_vel이라는 topic이 존재 하는 것을 볼 수 있다. /turtle1/rotate_absolute/_action/status, /turtle1/rotate_absolute/_action/feedback이라는 action도 보이지만 오늘의 주제는 아니므로 잠시 신경끄도록 하자!</p>
<p>또한 마우스를 topic 이름, /turtle1/cmd_vel 위에 올리면 위의 그림과 같이 색이 보이는 것(highlighting기능)을 확인할 수 있다. </p>
<p>이 그림에서 /turtlesim node와 /teleop_turtle이 /turtle1/cmd_vel이라는 topic을 통해서 메세지 전송이 일어남을 시각화하고 있다. 
즉, /teleop_turtle은 /turtle1/cmd_vel토픽에 message를 발행(publish)하고 /turtlesim은 이 topic을 구독(subscribe)함으로써 message를 획득한다.</p>
<p>앞서 언급한 highlighting 기능은 복잡한 시스템의 rqt_graph를 살펴볼 때 어떤 node와 topic들이 연결되있는지 볼 때 유용하다. </p>
<p>rqt_graph가 시각화 도구라면 terminal에서 메세지를 출력하는 방식의 도구도 있다. </p>
<hr>
<h3 id="3-ros2-topic-list-명령어">3. ros2 topic list 명령어</h3>
<p>터미널에 다음 명령어를 실행해보자</p>
<pre><code>ros2 topic list</code></pre><p>이 명령어는 현재 존재하는 모든 topic이름들을 나열해준다. </p>
<p>예시) /turtlesim, /teleop_turtle node에 있는 topic들 </p>
<pre><code>/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose</code></pre><p>추가로 -t라는(message type의 t) 명령어를 추가로 입력하면 topic에 발행되는 message의 type도 출력해준다.</p>
<pre><code>/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]
/turtle1/cmd_vel [geometry_msgs/msg/Twist]
/turtle1/color_sensor [turtlesim/msg/Color]
/turtle1/pose [turtlesim/msg/Pose]</code></pre><p>그런데 잠깐 이상한걸 눈치챈 독자들이 있는가?</p>
<p>ros2 topic list에 출력된 목록과 rqt_graph에 나왔던 topic들이 다르다. </p>
<p>그렇다. 사실 아까 rqt_graph에서 Hide라는 checkbox가 선택되어 있어서 일부 존재하지만 딱히 사용되지 않고 있은 topic들을 보이지 않았다. </p>
<p>너무 많으면 오히려 헷갈리니 다시 Hide를 체크해두자</p>
<hr>
<h3 id="4-ros2-topic-echo-명령어">4 ros2 topic echo 명령어</h3>
<p>topic에 발행되는 message를 확인하기 위해 다음과 같은 명령어를 사용한다. </p>
<pre><code>ros2 topic echo &lt;topic 이름&gt;</code></pre><p>앞서 rqt_graph에서 /teleop_turtle node가 /turtlesim node에 /turtle1/cmd_vel topic을 통해 message를 발행한다는 것을 알았다. </p>
<p>한 번 어떤 message인지 봐볼까?</p>
<p>터미널에 다음과 같은 명령어를 실행한다. </p>
<pre><code>ros2 topic echo /turtle1/cmd_vel</code></pre><p>?? 아무 message도 뜨질 않는다. </p>
<p>3초 생각해보자
1
2
3
!</p>
<p>그렇다 지금 우리는 속도를 나타내는 message를 보내고 있지 않기 때문이다. </p>
<p>echo 한 터미널이 잘 보이게 위치 시킨 후 </p>
<p>turtle_teleop_key를 실행한 터미널로가서 방향키를 눌러 속도를 나타내는 message를 보내보자! </p>
<p>이제 다음과 같은 메세지가 echo 명령어를 실행한 터미널에 출력되는 것을 볼 수 있을 것이다.</p>
<p>Hurray!!!</p>
<pre><code>linear:
  x: 2.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0
  ---</code></pre><p>rqt_graph로 가서 Debug라 적인 box를 클릭하여 uncheck해보자</p>
<p>echo도 사실 고유 이름을 갖는 임의의 node를 생성하여 /turtle1/cmd_vel topic을 구독하고 여기에 들어온 message를 출력해준다. </p>
<hr>
<h3 id="5-ros2-topic-info-명령어">5. ros2 topic info 명령어</h3>
<p>topic이 용이한 이유는 일대일 통신일 필요가 없어서 이다. 
즉, 일대다, 다대일, 다대다 통신이 가능하다는 것이다. wow</p>
<p>이걸 어떻게 아냐고?</p>
<p>새 터미널을 열어서 다음 명령어를 입력해보자. </p>
<p>이 명령어는 관심 topic의 정보를 제공한다. </p>
<pre><code>ros2 topic info /turtle1/cmd_vel</code></pre><pre><code>Type: geometry_msgs/msg/Twist
Publisher count: 1
Subscription count: 2</code></pre><p>이는 즉 /turtle1/cmd_vel라는 topic에 message를 발행하는 자(Publisher count)는 1명, 구독하는자는 2명임을 말해준다. </p>
<hr>
<h3 id="6-ros2-interface-show">6. ros2 interface show</h3>
<p>topic을 통해 node가 데이터를 주고 받는 다는 것을 확인했다. </p>
<p>그런데 아까 데이터를 주고 받는 node 속에는 발행자(이하 publisher)와 구독자(이하 subscriber)가 있다고 했는데 이들이 정상적으로 데이터를 주고 받으려면 같은 message type을 사용해야한다. </p>
<p>/turtle1/cmd_vel의 message type은 다음과 같았다. </p>
<pre><code>geometry_msgs/msg/Twist</code></pre><p>이 type의 의미를 풀이해보면 geometry_msgs 패키지 안에 msg 중 Twist라는 type이 있다는 이야기다. 
그렇다 패키지는 excutable, node뿐만 아니라 message(또는 msg)도 갖고 있을 수 있다. </p>
<p>또한, ros2 interface show &lt;message 타입&gt; 이라는 명령어로 message에 어떤 정보가 있는지 알 수 있다. </p>
<pre><code>ros2 interface show geometry_msgs/msg/Twist</code></pre><p>위의 명령어를 입력했다면 다음과 같은 정보가 출력될 것이다. </p>
<pre><code># This expresses velocity in free space broken into its linear and angular parts.

    Vector3  linear
            float64 x
            float64 y
            float64 z
    Vector3  angular
            float64 x
            float64 y
            float64 z</code></pre><p>의미를 살펴보자면 linear는 선속도(x,y,z축 방향으로), 그리고 angular는 각속도(각을 나타내는 축 또한 x,y,z축을 정의할 수 있다)를 의미한다.</p>
<p>그리고 각각의 속도는 Vector3라는 데이터 타입을 갖는데 이때 숫자 3은 성분이 x,y,z로써 3개라서 그렇다. </p>
<p>이 선속도, 각속도는 추후 자세하게 배울 것이니 그냥 로봇의 속도를 나타내는 message구나 라고 생각하면 된다. </p>
<hr>
<h3 id="7-ros2-topic-pub">7. ros2 topic pub</h3>
<p>topic 이름과 message type을 알고 있으면 </p>
<p>다음 명령어를 사용하여 터미널에서 해당 topic에 message를 임의로 발행(이하 publish)해줄 수 있다. </p>
<pre><code>ros2 topic pub &lt;topic 이름&gt; &lt;message 타입&gt; &#39;&lt;message 내용(데이터)&gt;&#39;</code></pre><p>그렇다면 아까 확인한 Twist라는 message를 다음과 같이 publish해보자</p>
<pre><code>ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist &quot;{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}&quot;</code></pre><p>명령어의 의미를 살펴보자면 /turtle1/cmd_vel 이라는 topic에 geometry_mags 패키지 안 msg 폴더에 있는 Twist message를 x축 선속도는 2.0, y축, z축 선속도는 0.0, x,y축 각속도는 0.0, z축 각속도는 1.8로 설정해서 publish해줘 라는 말이 된다. </p>
<p>이때 once는 딱 한번만 message를 publish하라는 말이다. </p>
<p>명령어를 입력했다면 다음과 같이 터미널에 표시될 것이다.</p>
<pre><code>publisher: beginning loop
publishing #1: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=2.0, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=1.8))</code></pre><p>그렇다면 TurtleSim 창에 있는 turtle을 다음과 같이 움직였을 것이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/e5996c14-10d1-4625-9581-26a5a0d08cdb/image.png" alt=""></p>
<p>메세지를 한번만 publish했기 때문에 일정 거리를 움직이고 멈추는 것을 관찰 할 수 있다. </p>
<p>계속적으로 움직이게 하고 싶다면 --once 대신 --rate라는 인자를 사용하면 된다. rate는 Hz(1초당 몇회를 나타내는 단위)를 의미한다. </p>
<p>명령어를 구성하자면 다음과 같다</p>
<pre><code>ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist &quot;{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}&quot;</code></pre><p>다른 부분은 다 같지만 --once 부분이 --rate 1 로 대체됐는데 이는 1Hz로 Twist message를 publish 계속 해줘 라는 말이다.</p>
<p>아래와 같이 로봇이 원형의 궤적을 그렸다면 성공!</p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/d9713d60-b06f-4e6d-9689-eeca7009d391/image.png" alt=""></p>
<p>이제 rqt_graph창으로가서 refresh 버튼을 눌러보면(파란색 원형 화살표) 새로운 publisher가 생긴 것을 확인 할 수 있다. </p>
<p>하단의 이미지는 /_ros2cli_30358이라는 node가 ros2 topic pub 명령어에 의해 생성되 /turtle1/cmd_vel topic에 publish하고 있는 모습을 보여준다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/39d424c9-fc61-4b4a-a039-456fecb4da81/image.png" alt=""></p>
<p>topic이 존재하지만 다른 node와 연결되지 않아 숨어있는 topic이있는데 그 중 하나가 /turtle1/pose 이다. 이는 turtle1의 자세 정보를 전달 및 수신하는데 사용된다.</p>
<p>그러면 ros2 topic echo를 이용해 새롭게 node를 만들어주면 rqt_graph에서는 다음과 같이 /_ros2cli_1682라는 node에 /turtle1/pose가 연결된 것을 확인 할 수 있다. </p>
<pre><code>ros2 topic echo /turtle1/pose</code></pre><p><img src="https://velog.velcdn.com/images/i_robo_u/post/ba4c3719-ae4f-4e1f-9b44-e0eb71f31a44/image.png" alt=""></p>
<hr>
<h3 id="8-ros2-topic-hz">8. ros2 topic hz</h3>
<p>마지막으로 ros2 topic hz라는 명령어가 있다. 
hz라는게 무슨 의미일까? 2초간 생각해보자.
1
2</p>
<p>그렇다 Hz(1초당 몇회)라는 단위에서 나온 것인데</p>
<p>다음과 같은 명령어를 사용하면 1초당 해당 topic이 몇 번 publish되는지 알 수 있다. </p>
<pre><code>ros2 topic hz &lt;topic 이름&gt;</code></pre><p>그렇다면 /turtle1/pose에 얼마나 message가 publish되는지 알기위해선 다음과 같은 명령어를 실행하면 된다. </p>
<pre><code>ros2 topic hz /turtle1/pose</code></pre><p>그러면 터미널에 다음과 같이 평균적으로 몇번이나 1초에 message가 publish되는지 알려준다. 
참고로 min max std dev 등은 message가 publish되는데 걸린 최소, 최대, 표준편차 시간을 의미한다.</p>
<pre><code>average rate: 59.354
  min: 0.005s max: 0.027s std dev: 0.00284s window: 58</code></pre><hr>
<p>여태까지 topic의 개념과 관련된 명령어 도구들을 엄청 배웠다. </p>
<p>ROS2로 인공지능/로봇 개발 하다보면 유용한 개념이라 엄청 사용하게 될 것이다. </p>
<p>그렇기 때문에 관련 도구들도 다양했다. </p>
<p>아직 낯설더라도 자주쓰다보면 손에 익혀지는 부분이니 너무 걱정하지 않아도 된다. </p>
<p>일단 여기까지 온 스스로에게 엄지척 해주자 ㅎㅎ </p>
<p>질문하고 싶거나 인공지능 &amp; 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주세요!</p>
<p><strong>문의메일</strong>: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
<strong>오픈톡 문의</strong>: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
<strong>IRoboU 유튜브 채널</strong>: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a></p>
<p><strong>참고 문헌</strong>: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics.html">https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/b1514575-fb89-4b21-92ec-0b1b2b022f6a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자와 함께하는 ROS2 Humble Node 이해하기, ROS2 명령어-2]]></title>
            <link>https://velog.io/@i_robo_u/ROS2-Humble-Node-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-2</link>
            <guid>https://velog.io/@i_robo_u/ROS2-Humble-Node-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-2</guid>
            <pubDate>Tue, 15 Aug 2023 10:17:32 GMT</pubDate>
            <description><![CDATA[<p>저번 포스트에서 ROS2 Humble(이하 ROS2)에서 사용되는 명령어 몇가지를 배웠다.</p>
<p>지난 포스트 링크: <a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">ROS2 명령어-1</a></p>
<p>오늘은 지난시간에 이어 추가로 node에 대한 명령어들을 배워보자</p>
<hr>
<h2 id="배경지식">배경지식</h2>
<h3 id="1-ros2-graph란">1. ROS2 graph란</h3>
<p>node 설명에 앞서 몇가지 배경지식을 쌓고가자. </p>
<p>ROS2 에는 ROS graph(이하 graph)라는 게 존재한다. 간단히 설명하자면 ROS2로 로봇개발을 하다보면 로봇내에서 메세지, 신호등의 교환이 일어나는데 이렇게 메세지, 신호들이 연결된 하나의 네트워크를 의미한다. </p>
<p>이 네트워크를 도식화 하게되면 네모, 동그라미 도형들이 선으로 연결된 것을 볼 수 있다. </p>
<h3 id="2-node란">2. Node란</h3>
<p>node는 특정 임무를 수행하는 로봇의 작은 구성원(모듈)들 중 하나라 생각할 수 있다. 예를 들어 임의의 node 1은 센서 데이터를 발행 할 수 있고, 또 다른 node 2는 모터 제어를 담당할 수 있다. </p>
<p>이때 node는 아래와 같이 topic, service, action, parameter등을 통해 다른 node와 정보 및 신호를 주고 받을 수 있다. </p>
<p>topic, service, action, parameter는 또다른 포스트에서 배워보겠다. </p>
<p>아래 보이는 그림과 같이 로봇 시스템은 여러 노드를 포함하게 된다. 
이때 executable(c++나 python으로 작성된 실행가능한 프로그램)은 한개 이상의 node를 포함 할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/8af2a60b-6d9a-4585-b147-996298d0e7df/image.gif" alt=""></p>
<p>위의 그림은 하나의 로봇 시스템을 도식화 한 것인데, 그림에서 보이듯이 다음을 관찰 할 수 있다.</p>
<ul>
<li>topic이라는 곳에 publisher가 message를 발행하면 subscriber들이 이를 구독한다.</li>
<li>service client가 서비스 서버로 하나의 서비스를 요청하면 service server는 이를 적절이 처리하고 다시 client로 응답을 보낸다. </li>
</ul>
<hr>
<h2 id="준비물">준비물</h2>
<p>지난 포스트에서 turtlesim과 rqt를 설치했어야한다. 
혹시 안했다면 이전 게시물을 확인하자!
<a href="https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1">turtlesim, rqt 설치 게시물 바로가기</a></p>
<hr>
<h3 id="목표">목표</h3>
<ul>
<li>node의 개념에 대해 배우고 node와 같이 사용되는 명령어를 익힌다.</li>
</ul>
<h3 id="예상-소요시간">예상 소요시간</h3>
<ul>
<li>약 10분</li>
</ul>
<h3 id="영상-튜토리얼">영상 튜토리얼</h3>
<ul>
<li>추후 업데이트</li>
</ul>
<hr>
<h2 id="1-ros2-run-명령어">1. ros2 run 명령어</h2>
<p>ros2 run 명령어는 패키지내 존재하는 executable(실행 프로그램 또는 실행 프로그램)을 실행(launch) 한다. </p>
<p>다음과 같은 문법을 갖는다. </p>
<pre><code>ros2 run &lt;패키지 이름&gt; &lt;실행 프로그램 이름&gt;</code></pre><p>turtlesim 패키지안에 있는 turtlesim_node라는 실행프로그램을 실행하려면 다음과 같이 명령어를 작성할 수 있겠다. </p>
<h4 id="tip-명령어-일부를-작성하고-tab-키를-누르면-나머지-부분이-자동완성되는-경우가-있다-명령어가-긴-경우-오타를-예방해주므로-적극적으로-활용하자">tip: 명령어 일부를 작성하고 Tab 키를 누르면 나머지 부분이 자동완성되는 경우가 있다! 명령어가 긴 경우 오타를 예방해주므로 적극적으로 활용하자.</h4>
<pre><code>ros2 run turtlesim turtlesim_node</code></pre><p>실행 프로그램이 켜졌지만 아직 node이름은 모른다. </p>
<p>위에서 배경지식에서 말했던 것 처럼 executable에는 하나 이상의 node가 있을 수 있다고 했는데 node이름은 어떻게 확인 하는 것 일까?</p>
<h2 id="2-ros2-node-list">2. ros2 node list</h2>
<p>ros2 run list라는 명령어를 터미널에 입력하면 현재 실행중인 node의 이름을 볼수 있다. </p>
<pre><code>ros2 node list</code></pre><p>그러면 터미널에는 다음과 같이 /turtlesim 이라는 이름을 갖는 node가 있다고 말할 것이다. </p>
<pre><code>/turtlesim</code></pre><p>다른 터미널을 열어서 또 다른 실행 프로그램 turtle_teleop_key를 켜보자 </p>
<pre><code>ros2 run turtlesim turtle_teleop_key</code></pre><p>다시 다른 터미널에서 ros2 node list를 입력하면 turtle_teleop_key 실행 프로그램에 의해 켜진 /teleop_turtle이라는 node를 볼 수 있을 것이다. </p>
<pre><code>/turtlesim
/teleop_turtle</code></pre><h3 id="21-remapping-실습">2.1 Remapping 실습</h3>
<p>Remapping이란 node 이름, node가 갖는 topic이름, service 이름등의 특성들을 사용자가 설정하는 값으로 변환 할 수 있는 기능을 말한다. </p>
<p>예를 들어 다음과 같은 명령어를 사용하면 /turtlesim이라는 node 이름 대신 /my_turtle 이라는 특정 이름을 지정해줄 수 있다. </p>
<pre><code>ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle</code></pre><p>따라서 ros2 node list를 다른 터미널에 입력해보면 다음과 같이 총 3개의 node가 실행된 것을 확인 할 수 있다. </p>
<pre><code>/my_turtle
/turtlesim
/teleop_turtle</code></pre><h2 id="3-ros2-node-info-실습">3. ros2 node info 실습</h2>
<p>일단 node 이름을 알면 node의 정보는 ros2 node info라는 명령어로 확인 가능하다. 
문법은 다음과 같다. </p>
<pre><code>ros2 node info &lt;node 이름&gt;</code></pre><p>그럼 해당 명령어를 활용하여 우리가 실행한 /my_turtle의 정보를 확인해보자</p>
<pre><code>ros2 node info /my_turtle</code></pre><p>다음과 같은 어마어마한 정보들이 터미널에 출력됐을 것이다. </p>
<pre><code>/my_turtle
  Subscribers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Publishers:
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /turtle1/color_sensor: turtlesim/msg/Color
    /turtle1/pose: turtlesim/msg/Pose
  Service Servers:
    /clear: std_srvs/srv/Empty
    /kill: turtlesim/srv/Kill
    /my_turtle/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /my_turtle/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /my_turtle/get_parameters: rcl_interfaces/srv/GetParameters
    /my_turtle/list_parameters: rcl_interfaces/srv/ListParameters
    /my_turtle/set_parameters: rcl_interfaces/srv/SetParameters
    /my_turtle/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
    /reset: std_srvs/srv/Empty
    /spawn: turtlesim/srv/Spawn
    /turtle1/set_pen: turtlesim/srv/SetPen
    /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute
    /turtle1/teleport_relative: turtlesim/srv/TeleportRelative
  Service Clients:

  Action Servers:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute
  Action Clients:</code></pre><p>보기만 해도 어질어질 한데 사실 내용을 별게 없다. </p>
<p>/my_turtle이라는 node가 갖고 있는 subscriber, publisher, service server, service client, action server, action client를 나열해주고 있다. 
이들은 나중에 topic, service, action에 대해 개념을 배우고 실습하고 나면 익숙해 질 것이니 지금 당장 각각 publisher, service server/client, action server/client가 뭐하는 건지 몰라도 걱정말자!</p>
<hr>
<p>여태까지 node란 무엇인가 개념을 알아보았고 이에 관련된 터미널 명령어도 살펴보았다. </p>
<p>완전히 이해하지 못 했는가?</p>
<p>전혀 상관없다. 반복해서 연습하다보면 지금 베일에 싸여있는 개념들이 하나씩 드러나기 시작할 것이다. </p>
<p>여기까지 왔다면 스스로를 칭찬해주자. 토닥토닥</p>
<p>질문하고 싶거나 인공지능 / 로봇 개발에 대한 올려줬으면 하는 주제를 댓글로 남겨주세요~</p>
<p><strong>문의메일</strong>: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
<strong>오픈톡 문의</strong>: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
<strong>IRoboU 유튜브 채널</strong>: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a></p>
<p>참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html">https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/e8bd0a88-7dd0-4755-9730-94a18e11e51a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[현직자가 알려주는 ROS2 명령어 도구(Command Line interface tools)-1]]></title>
            <link>https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1</link>
            <guid>https://velog.io/@i_robo_u/%ED%98%84%EC%A7%81%EC%9E%90%EA%B0%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-ROS2-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%8F%84%EA%B5%ACCommand-Line-interface-tools-1</guid>
            <pubDate>Sun, 13 Aug 2023 08:05:47 GMT</pubDate>
            <description><![CDATA[<p>저번시간에 ROS2 Humble(이하 ROS2)을 설치했다.</p>
<p>아직 설치 못한 사람이 있다면 아래 포스트를 확인해 보길 바란다. </p>
<p><a href="https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95">ROS2 Humble 설치방법</a></p>
<p>자 그럼 이번에는 무엇을 배울까? </p>
<p>ROS2에는 인공지능/로봇 개발할 때 자주 사용되는 명령어들이 몇가지 있다. </p>
<p>오늘은 이 명령어들을 배워보자. </p>
<hr>
<p><strong>목표</strong></p>
<ul>
<li>turtlesim 패키지 설치 및 사용법 배우기</li>
<li>rqt 설치 및 사용법 배우기</li>
</ul>
<p><strong>예상 소요시간</strong>
약 15분 </p>
<hr>
<p><strong>준비물</strong>
<a href="https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95">ROS2 Humble 설치방법</a> 에서 터미널을 켤 때 ROS2 환경이 설정되어야 한다. </p>
<hr>
<h3 id="1-turtlesim-설치하기">1. turtlesim 설치하기</h3>
<p>turtlesim 이란?
ROS2에는 topic, service, action 등등 로봇 개발에 필요한 주요 개념이 있다. 이를 배울 때 로봇이 당장 갖춰져있으면 좋겠지만 없을 때를 대비해서 친절한 누군가 개발해놓은 간단한 시뮬레이션이라고 생각하면 된다. </p>
<p>이전 포스트에서 말했듯이 ROS2로 로봇개발을 하다보면 추가로 설치해야 하는 패키지들이 종종 있다. 이번에는 turtlesim(아마 turtle robot simulation의 줄임말인듯!)을 설치해보자!</p>
<p>설치는 정말 간단하게 하단의 명령어들을 실행해주면 된다. </p>
<p>일단 업데이트 한번해주고~</p>
<pre><code>sudo apt update</code></pre><p>다음 명령어로 ROS2 humble에 맞는 turtlesim 설치 설치!</p>
<pre><code>sudo apt install ros-humble-turtlesim</code></pre><p>설치가 완료됐다면 ROS2 패키지가 잘 설치 됐는지 ros2 pkg executables라는 명령어를 통해 다음과 같이 확인한다. 이 명령어는 turtlesim 패키지에서 실행가능한 프로그램이 무엇인지 출력해준다. </p>
<pre><code>ros2 pkg executables turtlesim</code></pre><p>다음과 같은 목록이 터미널에 출력되면 설치 성공! Hurray~</p>
<pre><code>turtlesim draw_square
turtlesim mimic
turtlesim turtle_teleop_key
turtlesim turtlesim_node</code></pre><hr>
<h3 id="2-turtlesim-실행시키기">2. turtlesim 실행시키기</h3>
<p>이제 turtlesim 도 설치했겠다 본격적으로 몇가지 실습을 진행해보자!
먼저 turtlesim을 실행하려면 다음 명령어를 실행하면 된다.
이때 ros2 run은 다음과 같은 정보를 담아 명령어를 완성한다. </p>
<p><strong>ros2 run &lt;패키지 이름&gt; &lt;실행가능한 노드 이름&gt;</strong></p>
<p>그러면 다음 명령어는 turtlesim 이라는 패키지안에 turtlesim_node라는 노드를 실행하라는 의미를 갖는다. </p>
<p>근데 패키지가 뭐고 노드는 또 뭘까? </p>
<p>이는 추후에 또 설명하겠으나 패키지는 실행가능한 프로그램들의 묶음 정도로 이해하면 되고, 노드는 어떤 특정 역할을 수행하는 프로그램이라고 생각하면 되겠다. 지금 완전히 이해가 안되도 되니 걱정 No No!</p>
<p>명령어도 어느 정도 이해했겠다 일단 복붙 복붙!</p>
<pre><code>ros2 run turtlesim turtlesim_node</code></pre><p>명령어를 올바르게 실행했다면 다음과 같은 화면이 나올 것이다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/ad7c7453-8d6d-4934-a302-7b2d10f26c3a/image.png" alt=""></p>
<p>간혹 파란색 바탕화면에 다른 모양과 색을 띈 거북이가 나오는데, 걱정하지 않아도 된다. 매번 turtlesim_node를 실행할 때마다 약간의 랜덤성을 가지고 모양과 색이 결정된다. </p>
<p>동작하는데는 전혀 지장없다 ㅎㅎ</p>
<p>터미널에는 실행된 turtlesim_node로부터 turtle의 위치와 각도등을 알려주는 로그(메세지)가 출력 된 것을 확인 할 수 있을 것이다. </p>
<pre><code>[INFO] [turtlesim]: Starting turtlesim with node name /turtlesim
[INFO] [turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]</code></pre><hr>
<h3 id="3-turtlesim-사용하기">3. turtlesim 사용하기</h3>
<p>이번엔 turtlesim안에 있는 turtle을 turtle_teleop_key(아마 turtle tele operation with keyboard의 줄임말이 아닌가 싶다!)라는 node를 실행해서 움직여보자. </p>
<p>일단 다음 명령어로 turtle_teleop_key 노드를 실행시킨다. </p>
<pre><code>ros2 run turtlesim turtle_teleop_key</code></pre><p>그럼 다음과 같은 메세지가 출력될 것이다. </p>
<pre><code>Reading from keyboard
-------------------------------
Use arrow keys to move the turtle.
Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. &#39;F&#39; to cancel a rotation. 
&#39;Q&#39; to quit.</code></pre><p>간단히 설명하자면 </p>
<ul>
<li>위,아래 방향키로 turtle을 전진 후진 시킬 수 있다. </li>
<li>좌,우 방향키로 좌회전 우회전 시킬 수 있다. </li>
<li>G,B,V,C,D,E,R,T (소대문자 상관 없음)을 눌러 미리 설정된 각도로 회전시킬 수 있다.</li>
<li>F를 눌러 G,B,V,C,D,E,R,T에 의한 회전을 취소할 수 있다. </li>
<li>Q를 눌러 프로그램을 종료할 수 있다. </li>
</ul>
<p>이제 키보드를 가지고 turtle을 움직여보자. </p>
<p>이때 주의해야하는 점은 키보드 조작을 방금전 ros2 run turtlesim turtle_teleop_key를 실행 시킨 터미널을 클릭한 후 키보드를 눌러야 한다는 것이다.</p>
<p>TurtleSim이 적혀있는(파란바탕)화면을 클릭해놓은 채로 키보드를 움직여도 turtle은 움직이지 않는다 ㅠㅠ</p>
<p>아마 움직일 때 하얀색으로 turtle이 움직인 경로가 표시되는 것도 볼 수 있을 것이다. </p>
<p><strong>(선택)</strong>
추가적으로 새로운 터미널을 열어 다음 명령어들을 각각 입력해보자</p>
<pre><code>ros2 node list
ros2 topic list
ros2 service list
ros2 action list</code></pre><p>각각의 명령어의 역할을 다음과 같다.</p>
<ul>
<li>ros2 node list는 현재 실행 중인 node 목록을 보여준다</li>
<li>ros2 topic list는 현재 실행 중인 topic 목록을 보여준다.</li>
<li>ros2 service list는 현재 실행 중인 service 목록을 보여준다.</li>
<li>ros2 action list는 현재 실행 중인 action 목록을 보여준다.</li>
</ul>
<p>지금 node는 뭐고, topic, service, action이 뭔지 모르겠다고? 걱정할 필요 없다. 모르는게 당연하다. </p>
<p>추후 차근차근 상세하게 살펴볼 예정이다. </p>
<p>무튼 상단의 명령어를 실행했을 때 turtlesim_node와 turtle_teleop_key executable이 실행한 노드(/teleop_turtle, /turtlesim)가 제공한 topic, service, action이 무엇인지 출력해줄 것이다. </p>
<hr>
<h3 id="4-rqt-설치하기">4. rqt 설치하기</h3>
<p>여태까지 turtlesim을 설치하고 사용해 봤다면 이제는 rqt를 설치해보고 실행해 볼 것이다. </p>
<p>그 전에 기지개 한번 피도록 하자. 허이짜!</p>
<p>좋다 다시 본론으로 돌아와 rqt를 설치하기 위해서 다음 명령어를 실행한다. </p>
<p>습관적으로 업데이트 한번 해주고~</p>
<pre><code>sudo apt update</code></pre><p>아래 명령어에서 * 표시 있는데 이는 rqt에 관련된 프로그램을 같이 설치하라는 의미이다.</p>
<pre><code>sudo apt install ~nros-humble-rqt*</code></pre><hr>
<h3 id="5-rqt-실습">5. rqt 실습</h3>
<p>rqt를 실행하기 위해 다음 명령어를 새로운 터미널에서 실행한다.</p>
<pre><code>rqt</code></pre><p>명령어를 실행하게 되면 rqt에 대한 설명이 나와있다. 간단히 말해 여러 분석도구들을 보여주는 화면이라는 말을 하고 있는 것 같다. </p>
<p>그럼 상단 Plugins 탭에 가서 Services &gt; Service Caller 순으로 클릭하도록 하자.</p>
<p>그렇다면 어떤 창이 막 뜨는데 당황하지 말고 좌측 상단쯤에 있는 파란색 새로고침 화살표를 누르도록 하자. <img src="https://velog.velcdn.com/images/i_robo_u/post/e0fcf006-bbd7-4298-a002-8c28376a4d67/image.png" alt=""></p>
<p>그 후 call 옆에 있는 드랍다운을 누르면 현재 이용가능한 모든 service들을 볼 수 있다. 
<img src="https://velog.velcdn.com/images/i_robo_u/post/42333e81-1fba-444a-961e-cf9630fd82d9/image.png" alt=""></p>
<p> 그 다음엔 드랍다운에 나와있는 &#39;/spawn&#39; (소환) 이라는 이름을 선택한다. </p>
<p> 이 명령어는 또다른 turtle을 TurtleSim에 생성하는 것을 도와주는 servie이다. </p>
<hr>
<h4 id="51-spawn-service-실습">5.1 spawn service 실습</h4>
<p> 자 이제 &#39;/spawn&#39; service를 사용해보자. 
 먼저 새로운 turtle에 대한 생성 위치, 각도 이름을 설정해줘야한다. 
 expression 하단의 아이탬들을 더블클릭해서 다음과 같이 설정하자.</p>
<ul>
<li>x 는 1.0</li>
<li>y 는 1.0</li>
<li>theta 는 0.0</li>
<li>name은 &#39;turtle2&#39;</li>
</ul>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/6c4e7e5d-1e12-43bd-96b8-3840f5e462ef/image.png" alt=""></p>
<p>이제 아까 전화기 모양처럼 생긴 call 버튼을 눌러 내가 원하는 &#39;/spawn&#39; service를 요청하도록 하자!</p>
<p>이제 터미널로 가보면 새로운 turtle2가 생성된 것을 TurtleSim 화면에서 확인 할 수 있다.</p>
<p>하단과 비슷하게 turtle이 두마리 있으면 성공!</p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/654be365-8bac-4dcd-888a-6dcd35a5acaa/image.png" alt=""></p>
<hr>
<h4 id="52-set_pen-service-실습">5.2 set_pen service 실습</h4>
<p>이제 pen에 대한 정보를 설정하는 service인 set_pen service를 사용해보자 
이전 처럼 드랍다운을 눌러 /turtle1/set_pen 이라는 항목을 선택해보자.
<img src="https://velog.velcdn.com/images/i_robo_u/post/f7d3be28-9718-4e68-b9c3-bea139b187a5/image.png" alt=""></p>
<p>그 다음 원하는 대로 r, g, b, width값을 설정하면된다. 각각은 다음을 의미한다.</p>
<ul>
<li>r은 red 색의 정도. 0~255값을 갖는다.</li>
<li>g는 green 색의 정도. 0~255값을 갖는다.</li>
<li>b는 blue 색의 정도. 0~255값을 갖는다. </li>
<li>width는 pen의 두께이다. 0~255값을 이론적으로 갖을 수 있는 것 같다. </li>
</ul>
<p>참고로 지금 turtle1에 대한 pen 정보를 설정하고 있다.</p>
<p>설정이 끝났다면 이전처럼 call을 눌러 set_pen service를 요청하도록 하자!</p>
<hr>
<h3 id="6-remapping">6. Remapping</h3>
<p>여태까진 turtle1 밖에 못 움직였다는걸 눈치빠른 독자라면 인지하고 있었을 것이다.</p>
<p>그럼 turtle2는 어떻게 방법이 없을까?</p>
<p>다행히도 ROS2 개발자들이 이런 상황을 고려해 Remapping이라는 기능을 구현해놓았다. </p>
<p>Remapping이란 간단히 말해 기존 topic에 발행되던 메세지를 다른 특정 topic에 발행 될 수 있도록 매핑한다는 뜻이다. </p>
<p>명령어로 살펴보자면, </p>
<pre><code>ros2 run turtlesim turtle_teleop_key --ros-args --remap turtle1/cmd_vel:=turtle2/cmd_vel</code></pre><p>위 명령어는 turtlesim 패키지에서 turtle_teleop_key라는 executable을 실행할 때 ros argument들을 설정해줄건데 그중 remap이라는 argument는 turtle1/cm_vel 이라는 topic에 발행되는 메세지들을 turtle2/cmd_vel에 발행되도록 remmaping하게 설정한다. </p>
<p>라는 의미이다. </p>
<p>이 명령어를 사용해서 새로운 터미널에 turtle_teleop_key를 실행하면 하단 그림
처럼 turtle2를 움직일 수 있다! 야호~</p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/e25b8f60-0f12-4bbd-8fad-3905831edc55/image.png" alt=""></p>
<hr>
<p>여태까지 turtlesim, rqt 등을 설치하고 사용해봤다.</p>
<p>여러 개념이 나왔지만 다 이해하지 못 했다고 해서 너무 걱정할 필요 없다. </p>
<p>여기까지 왔다면 일단 스스로를 칭찬해주자 토닥토닥</p>
<p>어짜피 반복 숙달의 문제이므로 주저하지 말고 다음 포스트로 넘어가도 좋다! </p>
<p>질문하고 싶은 것은 댓글로 남겨주기 바랍니당~!</p>
<p>문의메일: <a href="mailto:irobou0915@gmail.com">irobou0915@gmail.com</a>
오픈톡 문의: <a href="https://open.kakao.com/o/sXMqcQAf">https://open.kakao.com/o/sXMqcQAf</a>
IRoboU 유튜브 채널: <a href="https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg">https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg</a></p>
<p>참고 문헌: <a href="https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Introducing-Turtlesim/Introducing-Turtlesim.html">https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Introducing-Turtlesim/Introducing-Turtlesim.html</a></p>
<p><img src="https://velog.velcdn.com/images/i_robo_u/post/0b66c317-ee99-49d6-934b-ec49ba7757cd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[현직자가 알려주는 ROS2 Humble 설치방법]]></title>
            <link>https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@i_robo_u/ROS2-Humble-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sat, 12 Aug 2023 08:36:52 GMT</pubDate>
            <description><![CDATA[<p>ROS wiki에 설치 방법이 잘 나와있지만 초심자에겐 뭐가 뭔 말인지 모를 수가 있다. 
특히 영어를 번역한 한글이면 더더욱!</p>
<p>로봇 개발을 하다보면 ROS2를 종종 재설치해야 하는 경우가 있는데 그럴 때를 대비해서 자료를 하나 정리해두기로 했다. 
IRoboU 유튜브 채널에 ROS2 Humble 설치 영상도 있으니 확인해보자!</p>
<p><strong>동영상 링크: <a href="https://youtu.be/4SRsKglF-ug">https://youtu.be/4SRsKglF-ug</a></strong></p>
<ul>
<li><strong>그런데 왜 humble사용해야 할까?</strong> 포스트 작성 날짜 기준 Humble이 제품 수명 날짜(End of Life, EOL)이 2027년 5월로 가장 길기 때문이다.</li>
</ul>
<hr>
<h1 id="컴퓨터-사양">컴퓨터 사양</h1>
<img src="https://velog.velcdn.com/images/i_robo_u/post/0241d9e2-ac0e-48cb-9cb4-0ad67f582e4d/image.jpg" width="300" height="100">


<p>(설치 방법 설명)
<strong>컴퓨터 사양</strong>: ROS2 Humble(이하 Humble 또는 ROS2)을 설치하기 위해선 Ubuntu 22.04.3LTS(이하 Ubuntu)가 설치되어 있는 컴퓨터가 필요하다. 윈도우나 MacOS도 가능하지만 로봇 내부 컴퓨터에 Ubuntu가 설치되는 경우가 상당히 많아서 이글에서는 Ubuntu에설치하는 법을 배운다. </p>
<p>Ubuntu 22.04.3LTS가 없는 사람이라면 </p>
<ul>
<li>윈도우 버추얼 박스나 버추얼머신을 설치해서 Ubuntu을 설치한다.(Ubuntu 설치 방법이 궁금한 분은 댓글 남겨주면 관련 자료를 만들어보겠다) </li>
<li>급한대로 일단 윈도우에 설치한다.</li>
<li>급한대로 일단 MacOS에 설치한다. </li>
</ul>
<hr>
<h1 id="ros2-설치-및-환경설정">ROS2 설치 및 환경설정</h1>
<img src="https://velog.velcdn.com/images/i_robo_u/post/453d7dce-006e-439c-a452-975b32cc8927/image.jpg" width="400" height="100">



<p>그렇다면 이제 컴퓨터가 준비되었다! 벌써 절반은 온 듯! 나머지는 천천히 명령어만 실행하면 설치가 끝난다. 생각보다 쉽네 ㅎㅎ</p>
<ul>
<li><p>먼저 터미널을 실행한다.</p>
</li>
<li><p>다음 두 명령어들을 실행해 Ubuntu Universe repository를 활성화한다. </p>
<pre><code>sudo apt install software-properties-common
</code></pre></li>
</ul>
<pre><code></code></pre><p>sudo add-apt-repository universe</p>
<pre><code>
* apt를 이용하여 ROS 2 GPG key를 추가한다. 이 과정들은 ROS2를 어디서 다운 받을지 정하는 일련의 과정이라고 생각하면된다.</code></pre><p>sudo apt update &amp;&amp; sudo apt install curl -y</p>
<pre><code></code></pre><p>sudo curl -sSL <a href="https://raw.githubusercontent.com/ros/rosdistro/master/ros.key">https://raw.githubusercontent.com/ros/rosdistro/master/ros.key</a> -o /usr/share/keyrings/ros-archive-keyring.gpg</p>
<pre><code>
* 그 다음, 소스 리스트에 ros repository를 추가해준다. 일종에 저장소가 어디있는지 알려주는 역할을 한다.</code></pre><p>echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] <a href="http://packages.ros.org/ros2/ubuntu">http://packages.ros.org/ros2/ubuntu</a> $(. /etc/os-release &amp;&amp; echo $UBUNTU_CODENAME) main&quot; | sudo tee /etc/apt/sources.list.d/ros2.list &gt; /dev/null</p>
<pre><code>
* 이제 본격적으로 ROS2를 설치할 차례이다. 
* 먼저 repository를 이전단계에서 설정해줬으므로 repository caches를 업데이트 해준다.</code></pre><p>sudo apt update</p>
<pre><code>
* ROS2는 자주 업데이트 되는 Ubuntu에 맞춰서 같이 업데이트 된다고 생각하면 편하다. 따라서 같이 업그레이드 한번해준다.
</code></pre><p>sudo apt upgrade</p>
<pre><code>
* 바로 이 명령어부터 ROS2를 설치해준다. ROS2내에도 여러가지 프로그램들이 있는데 이 명령어의 경우 ROS, RViz, demos, tutorial등등을 설치해준다. 차근차근 관련 내용을 포스트 할 것이니 너무 두려워하지 않아도 된다. (필자가 처음 배울 때 덜덜 떨었던건 안 비밀ㅎㅎ) </code></pre><p>sudo apt install ros-humble-desktop</p>
<pre><code>
* ROS2로 package라는 것을 만들 때 필요한 프로그램도 다음 명령어를 실행해 설치하자. 명령어에 있는 ros-dev-tools가 뭔가 ROS로 개발할 때 필요한 툴들을 의미하는 것 같다!</code></pre><p>sudo apt install ros-dev-tools</p>
<pre><code>

* 이 밖에도 개발하다보면 설치해야할 프로그램들이 많이 있다. 그렇지만 그때 가서 필요할 때마다 하나씩 설치하도록하자. 

* 그 후 ROS2 환경을 terminal에 다음과 같이 불러온다.</code></pre><p>source /opt/ros/humble/setup.bash</p>
<pre><code>
* 매번 터미널을 켤때마다 setup.bash파일을 불러오는게 귀찮다면 .bashrc 파일을 다음과 열어 파일 맨 하단에 위 명령어를 붙여넣으면 된다. 그럼 터미널이 열릴 때 .bashrc파일 내용이 실행되면서 ROS2 환경이 세팅된다.  </code></pre><p>gedit ~/.bashrc</p>
<pre><code>
* 그럼 ROS2가 온전히 설치됐는지는 어떻게 확인할까?


----
# ROS2 설치 확인하기(i.e. ROS2 데모 실행하기)

&lt;img src=&quot;https://velog.velcdn.com/images/i_robo_u/post/f5c74446-b9d5-4731-887e-e9e0520d261f/image.jpg&quot; width=&quot;400&quot; height=&quot;100&quot;&gt;

* 새로운 터미널을 연다.
* 다음과 같이 ROS2 환경 설정한다.</code></pre><p>source /opt/ros/humble/setup.bash</p>
<pre><code>* 다음 커맨드를 실행한다. talker라는 node를 실행한다. 대체 뭔소린지 몰라도 지금은 몰라도 된다. 다만 뭔가 말하는 프로그램을 실행한거 같다.</code></pre><p>ros2 run demo_nodes_cpp talker</p>
<pre><code>* ROS2가 정상 설치됐다면 위의 명령어 실행 시 &quot;Publishing&quot;이라고 터미널에 뜰 것이다. 

* 또다른 터미널을 열고 ROS2환경 설정한다.</code></pre><p>source /opt/ros/humble/setup.bash</p>
<pre><code>* 말하는 사람을 실행했으니 listener라는 듣는 사람을 담당하는 프로그램을 실행한다. 다음 명령어를 열심히 복붙 복붙하자!</code></pre><p>ros2 run demo_nodes_py listener</p>
<pre><code>* ROS2가 정상 설치됐다면 위의 명령어 실행 시 &quot;I heard&quot;라고 터미널에 메세지가 출력될 것이다.

* 그럼 ROS2가 정상적으로 설치된 것이다. 야호!!!

* 설치가 잘 안되는 분들은 현상을 댓글로 남겨주면 최대한 답해보겠다!


**문의메일**: irobou0915@gmail.com
**오픈톡 문의**: https://open.kakao.com/o/sXMqcQAf
**IRoboU 유튜브 채널**: https://www.youtube.com/channel/UC2-d99PrBwJy15MjPa32zYg


참고자료: 
https://docs.ros.org/en/humble/Installation.html

&lt;img src=&quot;https://velog.velcdn.com/images/i_robo_u/post/59295196-9fb5-4e44-8867-77bdab256ac9/image.png&quot; width=&quot;400&quot; height=&quot;100&quot;&gt;

</code></pre>]]></description>
        </item>
    </channel>
</rss>