<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hy_k.log</title>
        <link>https://velog.io/</link>
        <description>로봇, 드론, SLAM, 제어 공학 초보</description>
        <lastBuildDate>Sun, 29 Sep 2024 00:41:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hy_k.log</title>
            <url>https://velog.velcdn.com/images/hy_k/profile/913ef1a4-fe91-4901-9c4d-81a69c4b76ba/image.webp</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hy_k.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hy_k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[C++] 함수 중복과 static 멤버]]></title>
            <link>https://velog.io/@hy_k/C-%ED%95%A8%EC%88%98-%EC%A4%91%EB%B3%B5%EA%B3%BC-static-%EB%A9%A4%EB%B2%84</link>
            <guid>https://velog.io/@hy_k/C-%ED%95%A8%EC%88%98-%EC%A4%91%EB%B3%B5%EA%B3%BC-static-%EB%A9%A4%EB%B2%84</guid>
            <pubDate>Sun, 29 Sep 2024 00:41:31 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 함수의 중복과 static 멤버에 대해서 알아볼 것이다.</p>
<hr>
<h1 id="함수-중복">함수 중복</h1>
<blockquote>
<p>💡 ** 함수 중복(function overloading) **
C++에서는 C와 다르게 함수를 여러 개를 만들 수 있으며, 이것을 함수 중복(function overloading)이라고 부른다. 함수 중복은 다형성(polymorphism)의 한 사례로 전역 함수(global function)와 멤버 함수(member function) 모두에 적용되며, 상속 관계에도 적용된다.</p>
</blockquote>
<blockquote>
<p>💡 ** 중복 함수의 조건 **</p>
</blockquote>
<ol>
<li>중복된 함수들의 이름이 동일해야 한다.</li>
<li>중복된 함수들의 매개변수 타입이나 매개변수 개수가 달라야 한다.</li>
<li>리턴 타입은 고려되지 않는다.</li>
</ol>
<p>위 조건을 보면 알 수 있겠지만,
** 함수 이름, 매개변수 타입, 매개변수 개수가 모두 같은 함수들끼리 리턴 타입만 다르게 설정하면, 그건 함수 중복이 아니라 컴파일 오류가 발생한다. **</p>
<p>그리고 함수 중복에 대한 판정과 호출은 컴파일 시간에 이루어지기 때문에, ** 함수 중복으로 인한 실행 시간 저하는 없다. **</p>
<p>예시 코드를 살펴보자.
다음 코드는 두 함수의 함수 중복을 구현한 예시이다.</p>
<pre><code>#include&lt;iostream&gt;
using namespace std;

int big(int a, int b) {
    if (a &gt; b) return a;
    else return b;
}

int big(int a[], int size) {
    int res = a[0];
    for (int i = 1; i &lt; size; i++) {
        if (res &lt; a[i]) res = a[i];
    }
    return res;
}

int main() {
    int array[5] = { 1,9,-2, 4,6 };
    cout &lt;&lt; big(2, 3) &lt;&lt; endl;
    cout &lt;&lt; big(array, 5) &lt;&lt; endl;
}</code></pre><p>이번 코드 역시 마찬가지로 두 함수의 중복을 구현한 사례이다.</p>
<pre><code>#include&lt;iostream&gt;
using namespace std;

int sum(int a, int b) {
    int s = 0;
    for (int i = a; i &lt;= b; i++)
        s += i;
    return s;
}

int sum(int a) {
    int s = 0;
    for (int i = 0; i &lt;= a; i++)
        s += i;
    return s;
}

int main() {
    cout &lt;&lt; sum(3, 5) &lt;&lt; endl;
    cout &lt;&lt; sum(3) &lt;&lt; endl;
    cout &lt;&lt; sum(100) &lt;&lt; endl;
}</code></pre><blockquote>
<p>💡 ** 객체 생성자, 소멸자의 중복? **</p>
</blockquote>
<ul>
<li>생성자 함수는 앞서 살펴보았듯 당연히 중복이 가능하다.</li>
<li>하지만 소멸자는 오직 한개만 존재하기 때문에, 그리고 매개변수가 존재하지 않기 때문에 근본적으로 중복이 불가능하다.</li>
</ul>
<h1 id="디폴트-매개변수">디폴트 매개변수</h1>
<blockquote>
<p>💡 ** 디폴트 매개변수(default parameter) **
함수를 호출할 때 매개변수에 값이 넘어오지 않는다면, 미리 정해진 디폴트 값을 받도록 선언한 매개변수를 ** 디폴트 매개변수 ** 라고 부른다.</p>
</blockquote>
<h3 id="디폴트-매개변수-선언">디폴트 매개변수 선언</h3>
<p>매개변수=디폴트 값 형식으로 선언한다.</p>
<pre><code>ex)
void star(int a=5);</code></pre><p>위 예시 같은 경우에는 호출 시에는 매개변수를 써도 좋고, 안써도 좋다. 매개변수를 쓰지 않으면 디폴트 값이 들어가기 때문이다.</p>
<p>이렇게 디폴트 매개변수에 디폴트 값 전달은 컴파일러에 의해 자동으로 처리된다.</p>
<blockquote>
<p>💡 ** 디폴트 매개변수 선언시 주의할 점 **
디폴트 매개변수를 가진 함수를 선언할 때는 반드시 오른쪽 끝에 몰아서 선언해야 한다.</p>
</blockquote>
<pre><code>void calc(int a, int b=5, int c, int d=0); // 컴파일 오류
void sum(int a=0, int b, int c); // 컴파일 오류</code></pre><blockquote>
<p>💡 ** 디폴트 매개변수의 처리 방법 **
컴파일러에서 함수의 매개변수를 처리할 때, 호출문에 나열된 실인자 값들을 앞에서부터 순서대로 함수의 매개변수에 전달하고 나머지는 디폴트로 전달한다. 따라서 디폴트 매개변수들은 우측으로 몰아서 작성해야 한다.</p>
</blockquote>
<p>예를 들어서 매개변수가 4개 선언되고, 그 중 디폴트 매개변수가 3개라면 여러 방법으로 부를 수 있다.</p>
<pre><code>void g(int a, int b=0, int c=0, int d=0);
g(10);
g(10,5);
g(10,5,20);
g(10,5,20,30);</code></pre><blockquote>
<p>💡 ** 포인터 매개변수의 디폴트 값 **
포인터 변수를 디폴트 매개변수로 선언할 때도 디폴트 값을 줄 수 있다.</p>
</blockquote>
<pre><code>void f(int *p=NULL);
void g(int x[]=NULL);
void h(const char* s = &quot;Hello&quot;);</code></pre><p>디폴트 매개변수는 다음과 같은 장점을 제공한다.</p>
<ul>
<li>함수 중복을 간소화 할 수 있음. 다양하게 호출이 가능함.</li>
</ul>
<p>하지만 디폴트 매개변수를 가진 함수를 작성할 때는 다음을 주의해야한다.</p>
<ul>
<li>디폴트 매개변수를 가진 함수는 같은 이름의 중복 함수들과 같이 선언될 경우 컴파일 오류가 발생한다.</li>
</ul>
<pre><code>Class Circle{
...
public:
    Circle(){radius=1;}
    Circle(int r){radius = r;}
    Circle(int r=1){radius = r;} // 중복된 함수 사용 불가
};</code></pre><p>예시 코드를 한번 살펴보자.</p>
<pre><code>#include&lt;iostream&gt;
using namespace std;

class MyVector {
    int* p;
    int size;
public:
    MyVector(int n = 100) {
        p = new int[n];
        size = n;
    }
    ~MyVector() { delete[]p; }
};

int main() {
    MyVector* v1, * v2;
    v1 = new MyVector();
    v2 = new MyVector(1024);

    delete v1;
    delete v2;
}</code></pre><p>위 코드는 다음 코드를 통폐합 해서 간단하게 만든 코드이다.</p>
<pre><code>class MyVector {
    int* p;
    int size;
public:
    MyVector() {
        p = new int[100];
        size = 100;
    }
    MyVector(int n) {
        p = new int[n];
        size = n;
    }
    ~MyVector() { delete[]p; }
};</code></pre><h1 id="함수-중복의-모호함ambiguous">함수 중복의 모호함(ambiguous)</h1>
<p>함수 호출이 모호한 경우 컴파일 오류를 발생한다.
함수 중복으로 인해서 발생할 수 있는 모호성은 다음 3가지가 있다.</p>
<blockquote>
<p>💡 ** 함수 중복으로 인한 모호성 **</p>
</blockquote>
<ul>
<li>형 변환으로 인한 모호성</li>
<li>참조 매개변수로 인한 모호성</li>
<li>디폴트 매개변수로 인한 모호성</li>
</ul>
<h3 id="형-변환으로-인한-모호성">형 변환으로 인한 모호성</h3>
<ul>
<li>함수의 매개변수 타입과 호출 문의 실인자 타입이 일치하지 않는 경우 컴파일러에서 형 변환(type conversion)을 실시<pre><code>double square(double a);
square(3); // 컴파일러에서 형 변환 발생</code></pre></li>
<li>컴파일러는 작은 크기의 타입에서 큰 크기의 타입으로 형 변환을 지속함<pre><code>char -&gt; int -&gt; long -&gt; float -&gt; double</code></pre></li>
<li>하지만, 중복된 함수가 작성되어 있는 경우 적절한 실인자의 타입에 맞는 함수가 없을 경우 컴파일 오류가 발생함<pre><code>square(float a);
square(double a);
</code></pre></li>
</ul>
<p>// 이럴 경우 int a에 맞는 함수 중복이 없기 때문에 컴파일 오류 발생</p>
<pre><code>- 컴파일러는 이를 float 타입으로 변환해야하는지, double 타입으로 변환해야하는지 판정해주지 못함

예시 코드를 보자.</code></pre><p>#include<iostream>
using namespace std;</p>
<p>float square(float a) {
    return a * a;
}</p>
<p>double square(double a) {
    return a * a;
}</p>
<p>int main() {
    cout &lt;&lt; square(3.0);
    cout &lt;&lt; square(3); // 오류 발생
}</p>
<pre><code>14번째 라인에서 오류가 발생한다.

### 참조 매개변수로 인한 모호성
- 일반적인 매개변수와 참조 매개변수가 동시에 선언되어있는 중복 함수가 존재할 경우, 모호성으로 인해 컴파일 오류가 발생할 수 있다.</code></pre><p>int add(int a, int b);
int add(int a, int &amp;b);</p>
<pre><code>예시 코드를 한번 살펴보면 다음과 같다.</code></pre><p>#include<iostream>
using namespace std;</p>
<p>int add(int a, int b) {
    return a + b;
}</p>
<p>int add(int a, int&amp; b) {
    b = b + a;
    return b;
}</p>
<p>int main() {
    int s = 10, t = 20;
    cout &lt;&lt; add(s, t);
}</p>
<pre><code>15번째 라인에서 오류가 발생한다.

### 디폴트 매개변수로 인한 모호성
디폴트 매개변수를 가진 함수가 보통의 매개변수를 가진 함수와 중복 작성될 때 모호성이 존재할 수 있다. 예시 코드를 살펴보자.</code></pre><p>#include<iostream>
#include<string>
using namespace std;</p>
<p>void msg(int id) {
    cout &lt;&lt; id &lt;&lt; endl;
}</p>
<p>void msg(int id, string s = &quot;&quot;) {
    cout &lt;&lt; id &lt;&lt; &quot;:&quot; &lt;&lt; s &lt;&lt; endl;
}</p>
<p>int main() {
    msg(5, &quot;Good Morning&quot;);
    msg(6);
}</p>
<pre><code>15번째 라인에서 모호성으로 인해 컴파일 오류가 발생한다.

&gt;💡 ** 추가 정보 **
마찬가지로, 포인터 변수 역시 모호함으로 인해 컴파일 오류가 발생할 수 있다. 배열의 이름은 포인터기 때문에 다음과 같이 작성할 수 없다.</code></pre><p>int add(int *p);
int add(int p[]);</p>
<pre><code>
# static의 개념
### static의 특성
&gt; 💡 ** static의 정의 **
static은 변수와 함수의 life cycle과 사용 범위(scope)를 지정하는 방식(storage clasS) 중 하나로, static으로 선언된 변수와 함수는 다음 특징을 가지게 된다.
- life cycle : 프로그램이 시작할 때 생성하고, 프로그램이 종료될 때 소멸
- 사용 범위 : 변수나 함수가 선언된 범위 내에서 사용, global과 local 구분
- 클래스를 포함해 C++의 모든 변수와 함수는 static으로 선언 가능하다.

Non-static 멤버들과 static 멤버들의 차이는 다음과 같다.</code></pre><p>Non-static 멤버 : 각 객체와 생명주기를 함께 한다.
static 멤버 : 객체가 생성되기도 전에 생성되서, 객체가 소멸해도 사라지지 않는다.</p>
<pre><code>그렇기 때문에 Non-static 멤버는 ** 인스턴스 멤버 ** 라고 부르고, static 멤버는 클래스당 하나만 생기고, 모든 객체들이 공유하고, ** 클래스 멤버 ** 라고 부른다.

### static 멤버 선언
- 간단히, 선언을 원하는 멤버 함수나 멤버 변수 앞에 static 지정자를 붙히면 된다. 클래스당 하나만 생성한다.
- 여기서 ** 정말 중요한 것은, static 멤버 변수는 변수의 공간을 할당받는 선언문이 추가적으로 필요한데, 반드시 클래스 외부의 전역 공간에 선언되어야만 한다. **
- 전역 공간에서의 선언문이 없을 경우 링크 오류가 발생한다. 이는 다수의 객체들이 static 멤버들을 공유하기 때문이다.

예를 들면 다음과 같다.</code></pre><p>class Person{
public:
    int money;
    void addMoney(int money){
        this-&gt;money+=money;
    }</p>
<pre><code>static int sharedMoney;
static void addShared(int n){
    sharedMoney+=n;
}</code></pre><p>};
int Person::sharedMoney = 10;
// 반드시 전역 공간에 생성</p>
<pre><code>
### static 멤버의 사용법
- 보통 멤버들과 동일하게 객체 이름(.)이나 객체 포인터(-&gt;)를 사용하면 된다.</code></pre><p>#include<iostream>
using namespace std;</p>
<p>class Person {
public:
    int money;
    void addMoney(int money) {
        this-&gt;money += money;
    }</p>
<pre><code>static int sharedMoney;
static void addShared(int n) {
    sharedMoney += n;
}</code></pre><p>};
int Person::sharedMoney = 10;</p>
<p>int main() {
    Person han;
    han.money = 100;
    han.sharedMoney = 200;</p>
<pre><code>Person lee;
lee.money = 150;
lee.addMoney(200);
lee.addShared(200);

cout &lt;&lt; han.money &lt;&lt; &#39; &#39; &lt;&lt; lee.money &lt;&lt; endl;
cout &lt;&lt; han.sharedMoney &lt;&lt; &#39; &#39; &lt;&lt; lee.sharedMoney &lt;&lt; endl;</code></pre><p>}</p>
<pre><code>- static 멤버는 클래스 당 하나만 존재하므로 클래스의 이름으로도 접근이 가능하다.
- 클래스 이름과 static 멤버 사이의 범위 지정 연산자(::)를 사용하여 접근한다.</code></pre><p>className::static_member</p>
<pre><code>위의 코드를 기반으로 예시를 들면 다음과 같다.</code></pre><p>Person::sharedMoney = 200;
Person::addShared(200);</p>
<pre><code>- 이렇게 클래스 이름과 범위 지정 연산자를 사용하면, 어떤 객체를 생성하기도 전에 static 멤버를 활용할 수 있다.

### static을 사용하는 이유?
- 전역 변수나 전역 함수를 클래스에 캡슐화 : 전역 변수와 전역 함수의 사용을 최소화 하고 OOP의 기본 원리를 추구할 수 있다.</code></pre><p>#include<iostream>
using namespace std;</p>
<p>class Math {
public:
    static int abs(int a) { return a &gt; 0 ? a : -a; }
    static int max(int a, int b) { return (a &gt; b) ? a : b; }
    static int min(int a, int b) { return (a &gt; b) ? b : a; }
};</p>
<p>int main() {
    cout &lt;&lt; Math::abs(-5) &lt;&lt; endl;
    cout &lt;&lt; Math::max(10, 8) &lt;&lt; endl;
    cout &lt;&lt; Math::min(-3, -8) &lt;&lt; endl;
}</p>
<p>```</p>
<ul>
<li>객체 사이의 공유 변수를 만들때</li>
</ul>
<h3 id="static-멤버-함수의-특징">static 멤버 함수의 특징</h3>
<h4 id="static-멤버-함수는-오직-static-멤버들만-접근이-가능하다">static 멤버 함수는 오직 static 멤버들만 접근이 가능하다.</h4>
<p>static 멤버 함수는 static 멤버 변수에 접근하거나, static 멤버 함수만 호출이 가능하다. 왜냐면 static 멤버들은 객체 생성 전에도 존재하고, 클래스 이름으로도 직접 호출이 가능한 독특한 특징을 가지고 있기 때문에 static 멤버 함수에서 non-static 멤버에 접근하는 것을 허용하지 않는 것이다.</p>
<p>하지만!!
이와 반대로 non-static 멤버들은 static 멤버에 접근하는 것에 대한 제약이 없다.</p>
<h4 id="static-멤버-함수는-this를-사용할-수-없다">static 멤버 함수는 this를 사용할 수 없다.</h4>
<p>static 멤버 함수는 객체가 생기기 전부터 호출이 가능하므로 this를 사용할 수 없다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 함수와 참조, 복사 생성자]]></title>
            <link>https://velog.io/@hy_k/C-%ED%95%A8%EC%88%98%EC%99%80-%EC%B0%B8%EC%A1%B0-%EB%B3%B5%EC%82%AC-%EC%83%9D%EC%84%B1%EC%9E%90</link>
            <guid>https://velog.io/@hy_k/C-%ED%95%A8%EC%88%98%EC%99%80-%EC%B0%B8%EC%A1%B0-%EB%B3%B5%EC%82%AC-%EC%83%9D%EC%84%B1%EC%9E%90</guid>
            <pubDate>Mon, 23 Sep 2024 23:33:56 GMT</pubDate>
            <description><![CDATA[<p>이번에는 C++의 함수, 참조, 복사 생성자에 대해서 공부를 해보자.</p>
<hr>
<h1 id="함수의-인자argument-전달">함수의 인자(argument) 전달</h1>
<h2 id="인자-전달-방식">인자 전달 방식</h2>
<p>고급 프로그래밍 언어에서 인자 전달 방식(argument passing)은 다음과 같다.</p>
<blockquote>
</blockquote>
<p>** - 값에 의한 호출(call by value) **</p>
<blockquote>
<p>호출하는 코드에서 넘겨주는 실인자 값이 함수의 매개변수에 복사되어 전달</p>
</blockquote>
<p>** - 주소에 의한 호출(call by address) **</p>
<blockquote>
<p>주소를 직접 포인터 타입의 매개변수에 전달
예를 들어 함수 호출 시 배열이 전달되는 경우 배열의 이름은 곧 포인터임으로 주소에 의한 호출이 자동으로 이루어짐</p>
</blockquote>
<p>예시 코드를 살펴보자. 먼저 ** 값에 의한 호출 ** 이다.</p>
<pre><code>#include &lt;iostream&gt;
using namespace std;

void swap(int a, int b){
    int tmp;

    tmp = a;
    a = b;
    b = tmp;
}

int main(){
    int m=2, n=9;
    swap(m, n);
    cout &lt;&lt; m &lt;&lt;&#39; &#39;&lt;&lt; n;
}</code></pre><p>이렇게 호출할 경우, 매개변수 a와 b가 swap 함수의 스택 메모리 영역에 생성되고 m, n 값이 a와 b에 복사된다. 이 후 a와 b 값은 서로 교환되지만, swap() 함수가 종료되면 같이 사라지고 m, n 값은 처음부터 끝까지 전혀 변화하지 않는다.</p>
<p>반면, ** 주소에 의한 호출 ** 은 다음과 같다.</p>
<pre><code>#include &lt;iostream&gt;
using namespace std;

void swap(int *a, int *b){
    int tmp;

    tmp = a;
    a = b;
    b = tmp;
}

int main(){
    int m=2, n=9;
    swap(&amp;m, &amp;n);
    cout &lt;&lt; m &lt;&lt;&#39; &#39;&lt;&lt; n;
}</code></pre><p>이번에는 포인터 매개변수가 swap() 함수의 스택 영역에 생성되고, m과 n의 주소가 a와 b에 전달되어 직접적인 주소를 가리킨다. 따라서 swap() 함수의 종료와 함께 a와 b가 사라지더라도 main() 스택의 m, n 변수는 swap 함수 호출 이후부터 값이 바뀐 상태로 남아있게 된다.</p>
<blockquote>
<p>💡 ** 두 호출 방식의 특징 **
** 1. 값에 의한 호출 특징 ** : 실인자 손상 x
** 2. 주소에 의한 호출 특징 ** : 의도적으로 함수 내 실인자 값 변경</p>
</blockquote>
<h2 id="함수-호출-시-객체-전달">함수 호출 시 객체 전달</h2>
<h3 id="값에-의한-호출-객체-전달">&quot;값에 의한 호출&quot; 객체 전달</h3>
<p>class를 통해 생성한 객체를, ** 값에 의한 호출 ** 로 전달하게 된다면, 다음과 같은 특징을 가지고 있다.</p>
<ol>
<li>return이 없는 함수면 원본 객체는 변하는게 하나도 없다.</li>
<li>객체의 크기가 클수록 복사 시간이 길어지게 된다.</li>
<li>객체를 매개변수로 가지는 함수의 경우 생성자는 실행되지 않고 소멸자는 실행된다.</li>
</ol>
<p>예시 코드를 살펴보자.</p>
<pre><code>#include &lt;iostream&gt;
using namespace std;

class Circle{
    int radius;
public:
    Circle(){ radius=1; cout&lt;&lt;&quot;생성자 실행&quot;&lt;&lt;radius&lt;&lt;endl; }
    Circle(int r){ radius = r; cout&lt;&lt;&quot;생성자 실행&quot;&lt;&lt; radius &lt;&lt; endl; }
    ~Circle() { cout&lt;&lt;&quot;소멸자 실행&quot; &lt;&lt; radius &lt;&lt;endl; }
    double getArea(){ return 3.14 * radius * radius; }
    int getRadius() { return radius; }
    void setRadius(int radius) {this-&gt;radius = radius;}
};

void increase(Circle c) { // 객체 c의 생성자는 실행되지 않음
    int r = c.getRadius();
    c.setRadius(r+1);
} // 객체 c의 소멸자 실행

int main() {
    Circle waffle(30);
    increase(waffle);
    cout &lt;&lt; waffle.getRadius() &lt;&lt; endl;
}</code></pre><p>이 코드를 실행하게 되면 waffle 객체의 반지름은 변화하지 않는다.</p>
<blockquote>
<p>💡 ** 왜 매개변수 객체는 생성자를 실행하지 않는가? **
이미 생성된 객체가 기본 생성자와 멤버 변수 값 등이 다를 경우, 생성자가 실행되면 매개변수 객체가 초기화 되면서 전달 받은 원본 상태를 잃어버리기 때문이다.
하지만 이러한 방식은 중대한 문제점이 있고, 후술할 것이다.</p>
</blockquote>
<h3 id="주소에-의한-호출로-객체-전달">&quot;주소에 의한 호출&quot;로 객체 전달</h3>
<p>이 방식으로 호출하면 생성자가 실행되지 않는 것에서 기인하는 문제점들을 해결할 수 있다. 포인터 변수를 사용하여 주소를 통해서 호출하기 때문에 원본 객체의 값도 변화하게 된다.</p>
<blockquote>
<p>💡 ** 주소에 의한 호출로 객체 전달 특징 **</p>
</blockquote>
<ol>
<li>원본 객체를 복사하는 시간 소모 없음</li>
<li>생성자 소멸자의 비대칭 실행 문제 없음</li>
<li>원본 객체 훼손 가능성을 가지고 있으므로 주의</li>
</ol>
<h2 id="객체-치환-및-객체-리턴">객체 치환 및 객체 리턴</h2>
<h3 id="객체-치환">객체 치환</h3>
<p>= 연산자를 이용해 객체끼리 치환하게 되면 객체의 모든 데이터를 비트 단위로 복사하게 된다. 하지만 두 객체는 여전히 다른 객체이며, 단지 데이터만 같을 뿐이다.</p>
<pre><code>Circle c1(30);
Circel c2(40);
c1 = c2;
// 두 객체는 다르나, c1의 내용물만 c2의 내용물과 같아졌을 뿐이다.</code></pre><h3 id="객체-리턴">객체 리턴</h3>
<p>함수가 객체를 리턴하게 되면 어떨까?</p>
<pre><code>Circle getCircle(){
    Circle tmp(30);
    return tmp;
}

Circle c;
c = getCircle();</code></pre><p>이 경우에 c는 tmp 객체로 치환되면서 반지름이 30으로 증가하게 된다. 객체 간의 복사가 일어나는 것이다.</p>
<h2 id="참조와-함수">참조와 함수</h2>
<blockquote>
<p>💡 ** 참조(reference) **
C언어에는 없으나, C++에서 도입된 고유한 개념으로 &amp; 기호를 통해서 사용한다. 이미 선언된 변수에 대한 alias이다.</p>
</blockquote>
<h3 id="참조-변수">참조 변수</h3>
<h4 id="선언">선언</h4>
<ul>
<li>참조 변수는 이미 선언된 변수에 대한 alias이며, &amp; 연산자를 이용해 선언함</li>
<li>선언 시 원본 변수로 초기화<pre><code>int n = 2;
int &amp;refint = n;
</code></pre></li>
</ul>
<p>Circle circle;
Circle &amp;refCircle = circle;</p>
<pre><code>- 별도 변수 공간 할당 x → 기본 변수 공간 공유
#### 사용
- 보통 변수와 동일하게 사용 → 원본 변수 사용하는 것과 동일
- 참조 변수에 대한 포인터도 만들 수 있음</code></pre><p>int *p = &rdfint;
*p = 20; // n = *p = refint = 20</p>
<pre><code>
#### 주의사항
- ** 초기화가 없다면 컴파일 오류 발생 **
- &amp;의 위치는 무관함</code></pre><p>int &amp;ref=n;
int&amp; ref=n;
int &amp; ref = n;</p>
<pre><code>- ** 참조 변수는 배열을 만들 수 없음 **
- ** 참조 변수에 대한 참조 선언이 가능함 **

### 참조에 의한 호출(call by reference)
참조 변수를 주로 사용하는 곳이 바로 **참조에 의한 호출**이다.
&gt;💡 ** 참조에 의한 호출 **
함수의 매개변수를 참조 변수로 선언하여, 매개변수가 함수 호출의 실인자를 참조하여 실인자와 공간을 공유하기 위한 인자 전달 방식</code></pre><p>void swap(int &amp;a, int &amp;b);</p>
<pre><code>이렇게 선언할 경우, 함수의 매개변수로 쓰인 참조 매개변수들은 함수의 스택 메모리에 별도 공간이 할당되지 않으며, main 함수의 스택에 할당되어 있는 변수들의 공간을 공유한다.
&gt;
그리고 함수 호출이 종료될 경우 참조 변수들의 이름은 그대로 사라지게 된다.

참조에 의한 호출과 그렇지 않은 호출을 한번 비교해보자.</code></pre><p>#include <iostream>
using namespace std;</p>
<p>bool average(int a[], int size, int&amp; avg) {
    if (size &lt;= 0) return false;
    int sum = 0;
    for (int i = 0; i &lt; size; i++)
        sum += a[i];
    avg = sum / size;
    return true;
}</p>
<p>int main() {
    int x[] = { 0,1,2,3,4,5 };
    int avg;
    if (average(x, 6, avg)) cout &lt;&lt; &quot;평균은 &quot; &lt;&lt; avg &lt;&lt; endl;
    else cout &lt;&lt; &quot;매개변수 오류&quot; &lt;&lt; endl;</p>
<pre><code>if(average(x, -2, avg)) cout &lt;&lt; &quot;평균은 &quot; &lt;&lt; avg &lt;&lt; endl;
else cout &lt;&lt; &quot;매개변수 오류&quot; &lt;&lt; endl;</code></pre><p>}</p>
<pre><code>💡 ** 참조에 의한 호출의 장점 **
- 주소에 의한 호출은 * 기호와 &amp; 연산자를 반복적으로 사용하므로 실수 가능성이 높고, 가독성이 떨어짐
- 참조에 의한 호출은 작성하기 쉬우며 가독성도 우수함

#### 참조에 의한 호출로 객체 전달
1. 값에 의한 호출로 객체를 매개변수에 전달할 경우
- 함수 내에서 매개변수 객체가 변하더라도 원본 객체는 변하지 않음
- 매개변수 객체의 생성자는 실행되지 않으며 소멸자만 실행됨

2. 참조에 의한 호출로 객체를 매개변수에 전달할 경우
- 참조 매개변수로 이루어진 모든 연산은 원본 객체도 변경
- 참조 매개변수는 이름만 생성하므로 생성자와 소멸자가 아예 작동 X
</code></pre><p>#include <iostream>
using namespace std;</p>
<p>class Circle{
    int radius;
public:
    Circle(){ radius=1; cout&lt;&lt;&quot;생성자 실행&quot;&lt;&lt;radius&lt;&lt;endl; }
    Circle(int r){ radius = r; cout&lt;&lt;&quot;생성자 실행&quot;&lt;&lt; radius &lt;&lt; endl; }
    ~Circle() { cout&lt;&lt;&quot;소멸자 실행&quot; &lt;&lt; radius &lt;&lt;endl; }
    double getArea(){ return 3.14 * radius * radius; }
    int getRadius() { return radius; }
    void setRadius(int radius) {this-&gt;radius = radius;}
};</p>
<p>void increase(Circle &amp;c) { // 참조에 의한 객체 생성
    int r = c.getRadius();
    c.setRadius(r+1);
}</p>
<p>int main() {
    Circle waffle(30);
    increase(waffle);
    cout &lt;&lt; waffle.getRadius() &lt;&lt; endl;
}</p>
<pre><code>다른 예시 코드를 보자.</code></pre><p>#include <iostream>
using namespace std;</p>
<p>class Circle {
    int radius;
public:
    Circle() { radius = 1; cout &lt;&lt; &quot;생성자 실행&quot; &lt;&lt; radius &lt;&lt; endl; }
    Circle(int r) { radius = r; cout &lt;&lt; &quot;생성자 실행&quot; &lt;&lt; radius &lt;&lt; endl; }
    ~Circle() { cout &lt;&lt; &quot;소멸자 실행&quot; &lt;&lt; radius &lt;&lt; endl; }
    double getArea() { return 3.14 * radius * radius; }
    int getRadius() { return radius; }
    void setRadius(int radius) { this-&gt;radius = radius; }
};</p>
<p>void readRadius(Circle&amp; c) {
    int r;
    cout &lt;&lt; &quot;정수 값으로 반지름을 입력하세요&gt;&gt;&quot;;
    cin &gt;&gt; r;
    c.setRadius(r);
}</p>
<p>int main() {
    Circle donut;
    readRadius(donut);
    cout &lt;&lt; &quot;donut의 면적 &gt;&gt; &quot; &lt;&lt; donut.getArea() &lt;&lt; endl;
}</p>
<pre><code>
### 참조 리턴
C++에서는 값(value) 말고도 참조를 리턴할 수 있다.</code></pre><p>char c = &#39;a&#39;;</p>
<p>char &amp; find(){
    return c; // 변수 c에 대한 참조를 리턴
}</p>
<p>char a = find(); // a = &#39;a&#39;가 된다.
char &amp;ref = fine(); // ref는 c에 대한 참조
ref = &#39;m&#39;; // c = &#39;m&#39;</p>
<p>find() = &#39;b&#39;; // c = &#39;b&#39;가 된다.</p>
<pre><code>참조를 리턴하기 때문에 사실상 변수공간을 리턴하는 것과 동일하다.
따라서 참조 변수를 리턴하는 함수들은 치환문 왼쪽에서 마치 변수처럼 사용할 수 있으며, 반대로 오른쪽에 올 경우 다른 변수에게 원 변수의 값을 치환하게 해준다.

예시 코드를 살펴보자.</code></pre><p>#include <iostream>
using namespace std;</p>
<p>char&amp; find(char s[], int index) {
    return s[index];
}</p>
<p>int main() {
    char name[] = &quot;Mike&quot;;
    cout &lt;&lt; name &lt;&lt; endl;</p>
<pre><code>find(name, 0) = &#39;S&#39;; // name[0]을 S로 변경
cout &lt;&lt; name &lt;&lt; endl;

char&amp; ref = find(name, 2);
// ref는 name[2]에 대한 참조
ref = &#39;t&#39;; // name = &#39;Site&#39;
cout &lt;&lt; name &lt;&lt; endl;</code></pre><p>}</p>
<pre><code>&gt; 💡 ** L-Value와 R-Value **
C++에서 치환 연산자(=)를 기준으로 왼쪽에 있는 것을 L-Value, 오른쪽에 있는 것을 R-Value라고 부른다.
- 왼쪽에는 공간이 와야한다.
- 오른쪽에는 값이 와야한다.
&gt;
그렇기 때문에 포인터 변수를 리턴하는 함수는 값을 반환하지 공간을 반환하는 것이 아니라 함수 그 자체는 왼쪽에 적을 수 없지만, 참조를 리턴하는 함수는 공간을 반환하기 때문에 왼쪽에도 함수 그 자체를 적을 수 있다.

## 복사 생성자
&gt; 💡 ** 얕은 복사(shallow copy)와 깊은 복사(depp copy) **
- ** 얕은 복사 **
&gt;
단순히 값만 복사하고, 소유권은 복사하지 않아 충돌이 발생하는 복사
- ** 깊은 복사 **
&gt;
값뿐만 아니라 소유권도 복사해서 충돌이 생기지 않는 복사

객체에 대해서도 얕은 복사와 깊은 복사를 구분할 수 있다.
- ** 얕은 복사 ** 의 경우 객체의 메모리를 공유하게 된다.
- ** 깊은 복사 ** 의 경우 별개 메모리를 가리키게 된다.

객체를 복사할 때 얕은 복사를 하게 된다면 메모리를 공유하기 때문에 사본 객체를 변경해도 원본 객체도 동일하게 변경되는 문제점이 발생한다.

### 복사 생성 및 복사 생성자
&gt; 💡 ** 복사 생성자 **
** 복사 생성 ** 이란 객체가 생성될 때 원본 객체를 복사해서 생성되는 경우로, C++에서는 복사 생성시 사용되는 복사 생성자(copy constructor)가 있다. 다음과 같이 선언한다.</code></pre><p>class ClassName{
    ClassName(const ClassName&amp; c);</p>
<pre><code>&gt;
복사 생성자의 특징은 다음과 같다.
- 복사 생성자의 매개변수는 하나만 사용한다.
- 자기 클래스에 대한 참조로 매개변수를 선언한다.
- 복사 생성자는 클래스에 오직 1개만 선언 가능하다.

&gt; 💡 ** 복사 생성자의 실행 **
- 복사 생성자는 치환 연산자(=)를 상정한 것이 아님
- 복사 생성자는 예를 들면 다음과 같이 호출해서 작동함</code></pre><p>Circle src(30);
Circle dest(src);
// src 객체를 복사해서 dest 객체 생성
// 복사 생성자 호출
// 별도 객체 공간 할당</p>
<pre><code>컴파일러에서 dest 객체가 생성될 때 복사 생성자를 호출하도록 컴파일 한다.

예시 코드를 살펴보자.</code></pre><p>#include <iostream>
using namespace std;</p>
<p>class Circle {
    int radius;
public:
    Circle(const Circle&amp; c) { this-&gt;radius = c.radius; cout &lt;&lt; &quot;복사 생성자 실행 &quot; &lt;&lt; radius &lt;&lt; endl; }
    Circle() { radius = 1; }
    Circle(int radius) { this-&gt;radius = radius; }
    double getArea() { return 3.14 * radius * radius; }
};</p>
<p>int main() {
    Circle src(30);
    Circle dest(src); // 복사 생성자 실행</p>
<pre><code>cout &lt;&lt; &quot;원본 면적 &quot; &lt;&lt; src.getArea() &lt;&lt; endl;
cout &lt;&lt; &quot;사본 면적 &quot; &lt;&lt; dest.getArea() &lt;&lt; endl;</code></pre><p>}</p>
<pre><code>
#### 디폴트 복사 생성자
일반적인 디폴트 생성자(default constructor)와 마찬가지로, 복사 생성자 역시 개발자가 별도 구문을 넣어놓지 않더라도, ** 컴파일러가 디폴터 복사 생성자(default copy constructor)를 묵시적으로 삽입 후 처리한다. **

하지만, 컴파일러가 삽입하는 디폴트 복사 생성자 코드는 ** 얕은 복사 ** 를 실행하도록 작동하는 코드이다.

왜냐하면 ** 컴파일러가 삽입한 복사 생성자는 원본 객체의 모든 멤버를 일대일로 사본(this)에 복사하도록 구성되기 때문이다. **

### 얕은 복사 생성자의 문제점
- 포인터 타입의 멤버 변수가 없으면 상관없다.
- 하지만 포인터 타입이 있을 경우, 복사할 경우 같은 메모리 주소를 가리키게 되므로 문제 발생 소지가 있다.
- 예시 코드를 통해 살펴보자.</code></pre><p>#include <iostream>
#include <cstring>
using namespace std;</p>
<h1 id="define-_crt_secure_no_warnings">define _CRT_SECURE_NO_WARNINGS</h1>
<p>class Person {
    char* name;
    int id;
public:
    Person(int id, const char* name);
    ~Person();
    void changeName(const char* name);
    void show() { cout &lt;&lt; id &lt;&lt; &#39;,&#39; &lt;&lt; name &lt;&lt; endl; }
};</p>
<p>Person::Person(int id, const char* name) {
    this-&gt;id = id;
    int len = strlen(name);
    this-&gt;name = new char[len + 1]; // 문자열 공간 할당
    strcpy(this-&gt;name, name); // name에 문자열 복사
}</p>
<p>Person::~Person() {
    if (name)
        delete[] name;
}</p>
<p>void Person::changeName(const char* name) {
    if (strlen(name) &gt; strlen(this-&gt;name))
        return; // 현재 할당된 메모리보다 긴 문자열은 다룰 수 없음
    strcpy(this-&gt;name, name);
}</p>
<p>int main() {
    Person father(1, &quot;KimHOUUUU&quot;);
    Person daughter(father); // 디폴트 복사 생성자 삽입 및 실행</p>
<pre><code>cout &lt;&lt; &quot;daughter 객체 실행 직후 ---&quot; &lt;&lt; endl;
father.show();
daughter.show();

daughter.changeName(&quot;Grace&quot;);
cout &lt;&lt; &quot;daughter 이름을 Grace로 변경한 후- --- &quot; &lt;&lt; endl;
father.show();
daughter.show();</code></pre><p>}</p>
<pre><code>위 코드를 살펴보면

1. father 객체 생성
2. 이를 복사한 daughter 객체 생성
3. 그런데 name은 포인터 변수이므로 동일 메모리를 가리키기에 두 객체가 동일한 문자열을 출력
4. daughter 객체의 문자열을 변경하면 father 객체까지 같이 변경됨. 동일 메모리를 가리키는 문제
5. 더 큰 문제는, daughter 객체가 소멸되고 나서 father 객체가 소멸될 때 ** 동일 메모리를 공유하므로 이미 반환된 메모리를 재반환 하게 되며 비정상 종료를 하는 문제가 일어남 **

따라서 ** 깊은 복사 생성자 ** 를 만들어야 이러한 문제점을 피할 수 있다.
예시 코드를 통해 알아보겠다.</code></pre><p>#define _CRT_SECURE_NO_WARNINGS</p>
<p>#include <iostream>
#include <cstring>
using namespace std;</p>
<p>class Person {
    char* name;
    int id;
public:
    Person(int id, const char* name);
    Person(const Person&amp; person);
    ~Person();
    void changeName(const char* name);
    void show() { cout &lt;&lt; id &lt;&lt; &#39;,&#39; &lt;&lt; name &lt;&lt; endl; }
};</p>
<p>Person::Person(int id, const char* name){
    this-&gt;id = id;
    int len = strlen(name);
    this-&gt;name = new char[len + 1]; // 문자열 공간 할당
    strcpy(this-&gt;name, name); // name에 문자열 복사
}
Person::Person(const Person&amp; person) {
    this-&gt;id = person.id; // id값 복사
    int len = strlen(person.name);
    this-&gt;name = new char[len + 1]; // name을 위한 공간 할당
    strcpy(this-&gt;name, person.name); // name의 문자열 복사
    cout &lt;&lt; &quot;복사 생성자 실행, 원본 객체의 이름 &quot; &lt;&lt; this-&gt;name &lt;&lt; endl;
}</p>
<p>Person::~Person() {
    if (name)
        delete[] name;
}</p>
<p>void Person::changeName(const char* name) {
    if (strlen(name) &gt; strlen(this-&gt;name))
        return; // 현재 할당된 메모리보다 긴 문자열은 다룰 수 없음
    strcpy(this-&gt;name, name);
}</p>
<p>int main() {
    Person father(1, &quot;KimHOUUUU&quot;);
    Person daughter(father); // 디폴트 복사 생성자 삽입 및 실행</p>
<pre><code>cout &lt;&lt; &quot;daughter 객체 실행 직후 ---&quot; &lt;&lt; endl;
father.show();
daughter.show();

daughter.changeName(&quot;Grace&quot;);
cout &lt;&lt; &quot;daughter 이름을 Grace로 변경한 후- --- &quot; &lt;&lt; endl;
father.show();
daughter.show();</code></pre><p>}</p>
<pre><code>이번에는 단순히 객체의 사본을 만들어내는 얕은 복사가 아니라, 아예 별도의 메모리 공간을 할당하는 깊은 복사가 이루어졌다.

그렇기 때문에 이 프로그램의 실행 결과 잘못된 메모리 반환 등의 문제점이 발생하지 않고, 두 객체의 공간이 겹치지도 않아 문자열 변경시 원본과 사본 모두 변경되는 등의 문제점이 발생하지 않는다.

### 묵시적 복사 생성
개발자가 명시하지 않아도, 묵시적으로 복사 생성자가 할당되는 경우들이 프로그램 개발 중에 종종 존재한다. 이때 깊은 복사 생성자가 존재하지 않는다면 프로그램이 비정상 종료할 가능성이 높다.

이러한 묵시적 복사가 발생하는 경우는 크게 3가지가 있다.

** 1. 객체를 초기화 하여 객체를 생성할 때 **</code></pre><p>Person son = fater ;</p>
<pre><code>이 경우 컴파일러는</code></pre><p>Person son(father);</p>
<pre><code>이 코드로 자동 변환하여 복사 생성자를 호출한다.</code></pre><p>Person son;
son = father;</p>
<pre><code>이 코드는 단순 치환문이기 때문에 복사 생성자가 호출되지 않는다. 오직 객체를 객체로 초기화 하여 객체가 생성될 때 발생하는 문제점이다.

** 2. &quot;값에 의한 호출&quot;로 객체가 전달될 때 **
함수의 매개변수 객체가 생성될 때 복사 생성자가 자동으로 호출된다.</code></pre><p>void f(Person person){ // 매개변수 person 생성시 복사 생성자 호출
....
}
Person father(1, &quot;Kitae&quot;);
f(father); // 값에 의한 호출</p>
<pre><code>값에 의한 호출은 생성자가 실행되지 않는다고 앞에서 얘기하였다. 생성자 대신에 복사 생성자가 실행되기 때문에 그렇다.

** 3. 함수가 객체를 리턴할 때 **
함수가 객체를 리턴할 때 return 문은 return 객체의 복사본을 생성하여 호출한 곳으로 전달하게 된다. 이때 복사 생성자가 호출된다.</code></pre><p>Person g(){
    Person mother(2, &quot;Jane&quot;);
    return mother;
} // mother의 복사본을 생성하여 리턴, 이때 복사 생성자 사용</p>
<p>```</p>
<p>따라서 이런 경우를 대비해서라도, 깊은 복사가 가능한 복사 생성자를 만들어놓는 것이 낫다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] ROS2 프로그래밍 설정들]]></title>
            <link>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%84%A4%EC%A0%95%EB%93%A4</link>
            <guid>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%84%A4%EC%A0%95%EB%93%A4</guid>
            <pubDate>Sun, 22 Sep 2024 23:33:08 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 RSO2 프로그램이 설정들에 대해서 알아볼 것이다.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24543">https://cafe.naver.com/openrt/24543</a></p>
<hr>
<h1 id="설정-스크립트setup-scripts">설정 스크립트(Setup Scripts)</h1>
<h3 id="setupbash-local_setupbash">setup.bash? local_setup.bash?</h3>
<p>ROS2 워크스페이스의 설정 스크립트는 setup.bahs와 local_setup.bash 2가지가 있다. 이에 대해서 비교하기 전에 먼저 알아야 할 개념이 있다.</p>
<blockquote>
<p>💡 ** underlay와 overlay **
** 1. underlay **</p>
</blockquote>
<ul>
<li>ROS2를 바이너리 설치하게 될 경우 /opt/ros/rosversion 폴더에 모든 구성 파일이 위치</li>
<li>ROS2를 소스로 빌드할 경우 ~/ros2_version/ 폴더와 같은 경로
이러한 개발 환경을 underlay라고 부름<blockquote>
</blockquote>
</li>
<li><ul>
<li><ol start="2">
<li>overlay **</li>
</ol>
</li>
</ul>
</li>
<li>개발자가 사용하는 워크스페이스를 overlay라고 부름</li>
<li>사용자 정의, 프로젝트 관련 패키지 등등</li>
<li>따라서 overlay 개발환경은 underlay 개발환경에 종속이 되게 됨</li>
</ul>
<h3 id="setupbash와-local_setupbash-사용법">setup.bash와 local_setup.bash 사용법</h3>
<blockquote>
<p>💡 ** local_setup.bash **
이 스크립트가 위치해있는 접두사(Prefix) 경로에 위치한 모든 패키지에 대한 환경을 설정하며, 상위 환경 혹은 상위 작업 공간에 대한 설정은 포함하지 않는다.</p>
</blockquote>
<blockquote>
<p>💡 ** setup.bash **
현재 작업 공간이 빌드될 때 환경에 제공된 다른 모든 작업 공간에 대한 local_seutp.bash를 포함한다. 즉, underlay 작업공간에 대한 설정까지 포함한다.</p>
</blockquote>
<p>워크스페이스가 1개라면 구분이 상관없지만, 여럿이라면 각 목적에 맞게 사용해야한다.</p>
<h1 id="ros_domain_id-vs-namespace">ROS_DOMAIN_ID vs Namespace</h1>
<p>ROS2를 사용하며 동일 네트워크를 공유한다면 서로 다른 사람이 만들어놓은 노드라고 할지라도 동일 네트워크 상에서는 접근 및 데이터 공유가 가능하다. 이를 방지하기 위해서는 다음과 같은 3가지 세팅을 사용할 수 있다.</p>
<blockquote>
<p>💡 ** 네트워크 설정 **
** 1. 물리적으로 다른 네트워크 사용 **</p>
</blockquote>
<p>** 2. ROS_DOMAIN_ID를 통해 DDS의 Domain 변경 **
이는 ROS2에서 DDS와 멀티캐스트 방식의 도입으로 가능하게 된 것으로, 다음 명령어를 사용하면 된다.</p>
<pre><code>$ export ROS_DOMAIN_ID=&lt;Int_Value&gt;</code></pre><p>보통 0~101의 정수 중 하나를 사용한다.</p>
<blockquote>
</blockquote>
<ol start="3">
<li>** 각 노드/토픽/서비스/액션 등의 이름에 Namespace 추가 **<blockquote>
</blockquote>
노드/토픽/서비스/액션/파라미터 등의 고유 이름에 Namespace를 붙여주면 독립적으로 자신만의 네트워크를 그룹화 할 수 있다. 이는 2가지 방법으로 가능하다.<blockquote>
</blockquote>
</li>
</ol>
<p>** 3-1. launch 파일에서 node_namespace 항목을 추가
3-2. run 명령어를 통해 노드를 실행시킬 때 --ros-args -r __ns:=&quot;namespace_name&quot; 인자를 추가해서 실행 **</p>
<blockquote>
</blockquote>
<p>다만, 2번째 방법의 경우 그룹화를 시켜서 다른 그룹과 겹치지 않게 하는 것 뿐이지, 동일 네트워크의 다른 사용자가 해당 그룹의 정보는 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] Launch 프로그래밍]]></title>
            <link>https://velog.io/@hy_k/ROS2-Launch-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@hy_k/ROS2-Launch-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Sat, 21 Sep 2024 14:17:07 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 Launch 프로그래밍에 대해서 다뤄보자.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24735">https://cafe.naver.com/openrt/24735</a></p>
<hr>
<h1 id="ros2-launch">ROS2 Launch</h1>
<p>ROS2에서 단일 노드를 실행하기 위해서는</p>
<pre><code>$ ros2 run &lt;pkg_name&gt; &lt;node_name&gt;</code></pre><p>명령어를 사용한다.</p>
<p>하지만 만약, 복수의 노드를 실행시키고 싶으면 Launch 시스템을 활용해야한다.
뿐만 아니라, Launch 시스템은 다음과 같은 기능을 제공한다.</p>
<blockquote>
<p>💡 ** Launch 시스템의 기능 **</p>
</blockquote>
<ol>
<li>복수의 노드를 한번에 실행</li>
<li>노드 실행 시 매개변수, 노드 이름, 노드 네임스페이스, 환경변수 등 다양한 설정</li>
</ol>
<p>ROS2의 Launch 시스템은 다음과 같은 파일 양식을 지원한다.</p>
<blockquote>
<p>💡 ** ROS2 Launch 시스템 파일 양식 **</p>
</blockquote>
<ol>
<li>XML 파일 → ROS1에서 계승</li>
<li>YAML 파일 → 많이 사용되진 않음</li>
<li>파이썬 파일(launch.py) → 쉽고 강력함</li>
</ol>
<hr>
<h3 id="launch-파일-작성하기">Launch 파일 작성하기</h3>
<p>launch 파일은 복수개의 노드와, 노드에서 사용할 파라미터 폴더 등을 불러오는 강력한 기능을 지원한다. 그리고 ROS2에서는 파이썬에 기반한 launch 파일(launch.py) 작성을 지원하기 때문에 ROS1 대비 작성이 쉬워졌다.</p>
<p>예시 launch 파일 코드는 다음과 같다.</p>
<pre><code>#!/usr/bin/env python3

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    param_dir = LaunchConfiguration(
        &#39;param_dir&#39;,
        default=os.path.join(
            get_package_share_directory(&#39;topic_service_action_rclpy_example&#39;),
            &#39;param&#39;,
            &#39;arithmetic_config.yaml&#39;))

    return LaunchDescription([
        DeclareLaunchArgument(
            &#39;param_dir&#39;,
            default_value=param_dir,
            description=&#39;Full path of parameter file&#39;),

        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;argument&#39;,
            name=&#39;argument&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),

        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;calculator&#39;,
            name=&#39;calculator&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),
    ])</code></pre><p>하나하나 차근차근 살펴보자.</p>
<h4 id="generate_launch_description">generate_launch_description</h4>
<pre><code>def generate_launch_description():

    xxx = LaunchConfiguration(yyy)

    return LaunchDescription([
        DeclareLaunchArgument(aaa),
        Node(bbb),
        Node(ccc),
    ])</code></pre><p>기본으로 사용하는 메서드이다. 이 메서드는 LaunchConfiguration 클래스를 이용하여 노드 실행 파일 관련 설정들을 초기화 하고, 리턴값으로 LaunchDescription 클래스를 반환하게 된다.</p>
<p>그리고 LaunchConfiguration 클래스의 생성자를 통해 파라미터 파일이 저장된 경로를 설정해줄 수 있다.</p>
<pre><code>def generate_launch_description():
    param_dir = LaunchConfiguration(
        &#39;param_dir&#39;,
        default=os.path.join(
            get_package_share_directory(&#39;topic_service_action_rclpy_example&#39;),
            &#39;param&#39;,
            &#39;arithmetic_config.yaml&#39;))</code></pre><h4 id="launchdescription">LaunchDescription</h4>
<p>DeclareLaunchArgument 클래스를 통해서 위에서 설정한 파라미터 변수 param_dir을 기본적인 launch argument로 설정할 수 있다.</p>
<pre><code>    return LaunchDescription([
        DeclareLaunchArgument(
            &#39;param_dir&#39;,
            default_value=param_dir,
            description=&#39;Full path of parameter file&#39;),</code></pre><p>그리고 Node 클래스로 실행할 노드를 설정하면 된다.
기본적으로 다음 5가지를 기재한다.</p>
<p>**</p>
<ol>
<li>package : 실행할 패키지 이름 기재</li>
<li>executable : 실행 가능한 노드의 이름을 기재</li>
<li>name : 지정한 노드를 실행할 때 실제로 사용할 이름을 기재</li>
<li>parameter : 특정 파라미터 값을 넣어도 되고, DeclareLaunchArgument에서 지정한 변수를 사용해도 된다.</li>
<li>output : 로깅 설정, 기본적으로 특정 파일 이름으로 된 로그 파일에 로깅 정보가 기록된다. screen으로 output을 지정하면 스크린에도 출력된다.</li>
</ol>
<p>**</p>
<p>예시 코드를 살펴보자.</p>
<pre><code>    return LaunchDescription([
        DeclareLaunchArgument(
            &#39;param_dir&#39;,
            default_value=param_dir,
            description=&#39;Full path of parameter file&#39;),

        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;argument&#39;,
            name=&#39;argument&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),

        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;calculator&#39;,
            name=&#39;calculator&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),
    ])</code></pre><p>또 다른 launch 파일의 유용한 기능으로는 ** remapping ** 기능이 있다. 이는 고유 이름을 변경하는 것으로, 내부 코드 변경 없이 토픽, 서비스, 액션 등의 고유 이름을 변경할 수 있는 기능을 제공한다.</p>
<p>예시 코드를 보면 다음과 같다.</p>
<pre><code>        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;argument&#39;,
            name=&#39;argument&#39;,
            remappings=[
                (&#39;/arithmetic_argument&#39;, &#39;/argument&#39;),
            ]</code></pre><p>이 코드를 보면 remapping을 통해서 이름을 바꾸고 있음을 알 수 있다.</p>
<p>그리고 또 다른 유용한 기능으로 ** namespace ** 가 있다.</p>
<blockquote>
<p>💡 ** namespace **</p>
</blockquote>
<ul>
<li>노드, 토픽, 서비스, 액션, 파라미터 등과 같은 고유 이름을 그룹화 하기 위해서 사용</li>
<li>자신만의 네트워크 그룹화를 하고, 다른 namespace와의 충돌 등을 방지</li>
<li>ROS2에서 namespace를 사용하려면 --ros-args-r__ns:= 라는 CLI 옵션을 이용하거나</li>
<li>launch 파일을 실행시킬 때 namespace 항목을 변경하면 된다.</li>
<li>예시를 들면 다음과 같다.<pre><code>def generate_launch_description():
  ros_namespace = LaunchConfiguration(&#39;ros_namespace&#39;)
&gt;
  return LaunchDescription([
      DeclareLaunchArgument(
          &#39;ros_namespace&#39;,
          default_value=os.environ[&#39;ROS_NAMESPACE&#39;],
          description=&#39;Namespace for the robot&#39;),
&gt;
      Node(
          package=&#39;topic_service_action_rclpy_example&#39;,
          namespace=ros_namespace,
          executable=&#39;argument&#39;,
          name=&#39;argument&#39;,
          output=&#39;screen&#39;),</code></pre>ros_namespace 변수를 통해 namespace를 선언하고, 환경변수인 ROS_NAMESPACE를 읽어오도록 지시한 코드이다. 이 변수를 활용하기 위해서는 .bashrc 파일에 환경변수를 설정하거나 아니면 터미널마다 명령어를 입력해줘야 한다.</li>
</ul>
<p>또한 launch 파일은 ** generate_launch_description ** 함수의 return 값이 너무 많아진다고 판단되면 ** LaunchDescription의 add_action ** 함수를 통해 간결하게 표현할 수 있다. 예시 코드를 보면 다음과 같다.</p>
<pre><code>#!/usr/bin/env python3

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    param_dir = LaunchConfiguration(
        &#39;param_dir&#39;,
        default=os.path.join(
            get_package_share_directory(&#39;topic_service_action_rclpy_example&#39;),
            &#39;param&#39;,
            &#39;arithmetic_config.yaml&#39;))

    launch_description = LaunchDescription()

    launch_description.add_action(launch.actions.DeclareLaunchArgument(
        &#39;param_dir&#39;,
        default_value=param_dir,
        description=&#39;Full path of parameter file&#39;)

    argument_node = Node(
        package=&#39;topic_service_action_rclpy_example&#39;,
        executable=&#39;argument&#39;,
        name=&#39;argument&#39;,
        parameters=[param_dir],
        output=&#39;screen&#39;)

    calculator_node = Node(
        package=&#39;topic_service_action_rclpy_example&#39;,
        executable=&#39;calculator&#39;,
        name=&#39;calculator&#39;,
        parameters=[param_dir],
        output=&#39;screen&#39;)

    launch_description.add_action(argument_node)
    launch_description.add_action(calculator_node)

    return launch_description</code></pre><h4 id="다른-launch-파일-불러오기">다른 launch 파일 불러오기</h4>
<p>** get_package_share_directory ** 함수를 이용하면 특정 다른 패키지의 launch 파일을 불러와서 같이 실행이 가능하다. 예시 코드는 다음과 같다.</p>
<pre><code>from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.actions import LogInfo
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import ThisLaunchFileDir


def generate_launch_description():
    return LaunchDescription([
        LogInfo(msg=[&#39;Execute three launch files!&#39;]),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                [ThisLaunchFileDir(), &#39;/xxxxx.launch.py&#39;]),
        ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                [ThisLaunchFileDir(), &#39;/yyyyy.launch.py&#39;]),
        ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                [get_package_share_directory(&#39;bbbbb&#39;), &#39;/launch/zzzzz.launch.py&#39;]),
                // 여기서 다른 패키지의 launch 파일 불러오기
        ),
    ])</code></pre><hr>
<h3 id="패키지-빌드">패키지 빌드</h3>
<h4 id="rclcpp-계열">rclcpp 계열</h4>
<p>CMakeLists.txt 파일에 다음 내용을 기재해야한다.</p>
<pre><code>install(DIRECTORY
  launch
  DESTINATION share/${PROJECT_NAME}/
)</code></pre><h4 id="rclpy-계열">rclpy 계열</h4>
<p>setup.py 내용에 launch 파일, 그리고 필요하다면 파라미터 파일 폴더를 추가해줘야 한다.</p>
<pre><code>setup(
    name=package_name,
    version=&#39;0.1.0&#39;,
    packages=find_packages(exclude=[&#39;test&#39;]),
    data_files=[
        (&#39;share/ament_index/resource_index/packages&#39;, [&#39;resource/&#39; + package_name]),
        (share_dir, [&#39;package.xml&#39;]),
        (share_dir + &#39;/launch&#39;, glob.glob(os.path.join(&#39;launch&#39;, &#39;*.launch.py&#39;))),
        (share_dir + &#39;/param&#39;, glob.glob(os.path.join(&#39;param&#39;, &#39;*.yaml&#39;))),
    ],</code></pre><h3 id="패키지-빌드-및-실행">패키지 빌드 및 실행</h3>
<p>빌드의 경우</p>
<pre><code>$ colcon build
$ colcon build --symlink-install
$ colcon build --symlink-install --packages-select
$ colcon build --symlink-install --packages-up-to</code></pre><p>중에 하나 골라서 하면 되고,</p>
<p>실행의 경우</p>
<pre><code>$ ros2 launch &lt;pkg_name&gt; &lt;launch_file_name&gt;</code></pre><p>위 명령어를 사용하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] C++ 패키지 설계 및 프로그래밍(파라미터, 실행인자)]]></title>
            <link>https://velog.io/@hy_k/ROS2-C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%8B%A4%ED%96%89%EC%9D%B8%EC%9E%90</link>
            <guid>https://velog.io/@hy_k/ROS2-C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%8B%A4%ED%96%89%EC%9D%B8%EC%9E%90</guid>
            <pubDate>Sat, 21 Sep 2024 09:43:14 GMT</pubDate>
            <description><![CDATA[<p>이번에는 C++을 기반으로 하는 파라미터 및 실행인자 프로그래밍을 수행해보도록 하겠다.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24858">https://cafe.naver.com/openrt/24858</a>
<a href="https://cafe.naver.com/openrt/24861">https://cafe.naver.com/openrt/24861</a></p>
<hr>
<h1 id="파라미터">파라미터</h1>
<p>파라미터에 대한 설명은 이전 포스트들을 참조하자. 간단히 얘기하면, 서비스와 유사하게 작동하지만, 글로벌 변수처럼 기능하면서 노드들의 다양한 값과 특성을 실시간으로 변화시킬 수 있는 요소라고 할 수 있다. 그리고 ROS2에서는 모든 노드가 파라미터 서버를 가지고 있어서 내외부의 파라미터 클라이언트와의 실시간 통신이 가능하다.</p>
<hr>
<h3 id="파라미터-서버와-초기화">파라미터 서버와 초기화</h3>
<p>파라미터 서버에 파라미터를 등록하는 방법은 크게 4가지가 있다.</p>
<blockquote>
</blockquote>
<p>** 1. yaml 포맷의 파일 경로를 프로그램 실행 인자로 rclcpp::Node에 전달 **
** 2. ROS2 CLI를 이용한 파라미터 등록 **
** 3. rclcpp:Node의 declare, set 파라미터 함수 사용 **
** 4. 파라미터 클라이언트 API 사용 **</p>
<p>이 중 yaml 파일은 저번 파이썬 파라미터 프로그래밍에서도 알아보았다.</p>
<pre><code>/**: # namespace and node name
  ros__parameters:
    qos_depth: 30
    min_random_num: 0.0
    max_random_num: 9.0</code></pre><p>만약 namespace와 node 이름을 구체적으로 규정하고 싶다면, 다음과 같이 작성하면 된다.</p>
<pre><code>/namespace: 
  /node_name:
    ros__parameters:
      foo: 30
      bar: 0.0</code></pre><p>설정이 모두 끝난 파라미터 파일은 launch 파일에 포함시켜 노드를 실행할 때 불러오게 된다.</p>
<p>예를 들어 launch.py 파일을 다음과 같이 작성할 수 있다.</p>
<pre><code>import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    param_dir = LaunchConfiguration(
        &#39;param_dir&#39;,
        default=os.path.join(
            get_package_share_directory(&#39;topic_service_action_rclcpp_example&#39;),
            &#39;param&#39;,
            &#39;arithmetic_config.yaml&#39;))

    return LaunchDescription([
        DeclareLaunchArgument(
            &#39;param_dir&#39;,
            default_value=param_dir,
            description=&#39;Full path of parameter file&#39;),

        Node(
            package=&#39;topic_service_action_rclcpp_example&#39;,
            executable=&#39;argument&#39;,
            name=&#39;argument&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),

        Node(
            package=&#39;topic_service_action_rclcpp_example&#39;,
            executable=&#39;calculator&#39;,
            name=&#39;calculator&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),
    ])</code></pre><p>그리고 CMakeLists.txt 파일에 파라미터 폴더 경로를 꼭 지정해야만 한다.</p>
<pre><code># 중략

install(DIRECTORY launch param
  DESTINATION share/${PROJECT_NAME}
)

# 중략</code></pre><hr>
<h3 id="파라미터-클라이언트">파라미터 클라이언트</h3>
<p>예시 코드를 통해서 보도록 하자.</p>
<pre><code>  rclcpp::Subscription&lt;rcl_interfaces::msg::ParameterEvent&gt;::SharedPtr parameter_event_sub_;
  rclcpp::AsyncParametersClient::SharedPtr parameters_client_;</code></pre><p>Argument 클래스에서는, ParameterEvent 타입으로 선언된 토픽 서브스크라이버와 AsyncParametersClient 클래스의 스마트 포인터를 확인할 수 있다.</p>
<p>또한 Argument 클래스의 생성자를 보면, 먼저 파라미터를 선언하기 위해 declare_parameter 함수를 사용해 파라미터 이름과 초깃값을 지정, get_paramter 함수를 통해 파라미터 값을 읽어들이는 것을 확인할 수 있다.</p>
<pre><code>  this-&gt;declare_parameter(&quot;qos_depth&quot;, 10);
  int8_t qos_depth = this-&gt;get_parameter(&quot;qos_depth&quot;).get_value&lt;int8_t&gt;();
  this-&gt;declare_parameter(&quot;min_random_num&quot;, 0.0);
  min_random_num_ = this-&gt;get_parameter(&quot;min_random_num&quot;).get_value&lt;float&gt;();
  this-&gt;declare_parameter(&quot;max_random_num&quot;, 9.0);
  max_random_num_ = this-&gt;get_parameter(&quot;max_random_num&quot;).get_value&lt;float&gt;();
  this-&gt;update_parameter();</code></pre><p>또한 update_parameter 함수로 this 포인터를 통해 초기화 하고, 파라미터 서버에 어떤 이벤트가 발생하였을 때 콜백함수를 등록하고, 이를 통해 파라미터 변경을 확인할 수 있도록 코드를 작성하였다.</p>
<pre><code>void Argument::update_parameter()
{
  parameters_client_ = std::make_shared&lt;rclcpp::AsyncParametersClient&gt;(this);
  while (!parameters_client_-&gt;wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(this-&gt;get_logger(), &quot;Interrupted while waiting for the service. Exiting.&quot;);
      return;
    }
    RCLCPP_INFO(this-&gt;get_logger(), &quot;service not available, waiting again...&quot;);
  }

  auto param_event_callback =
    [this](const rcl_interfaces::msg::ParameterEvent::SharedPtr event) -&gt; void
    {
      for (auto &amp; changed_parameter : event-&gt;changed_parameters) {
        if (changed_parameter.name == &quot;min_random_num&quot;) {
          auto value = rclcpp::Parameter::from_parameter_msg(changed_parameter).as_double();
          min_random_num_ = value;
        } else if (changed_parameter.name == &quot;max_random_num&quot;) {
          auto value = rclcpp::Parameter::from_parameter_msg(changed_parameter).as_double();
          max_random_num_ = value;
        }
      }
    };

  parameter_event_sub_ = parameters_client_-&gt;on_parameter_event(param_event_callback);
}</code></pre><hr>
<h1 id="실행-인자-프로그래밍">실행 인자 프로그래밍</h1>
<p>C++ 프로그램 실행 시 가장 먼지 호출되는 main 함수의 경우 2개의 매개변수를 가지고 있다.</p>
<blockquote>
<p>💡 ** main 함수의 매개변수 **</p>
</blockquote>
<ol>
<li>** argc ** : argument count의 약자로 넘겨받은 인자들의 개수</li>
<li>** argv ** : argument vector의 약자로 문자열/포인터/배열 타입으로 넘겨받은 인자들을 저장</li>
</ol>
<p>한편 ROS2의 실행 인자는 다음과 같이 구분된다.</p>
<blockquote>
<p>💡 ** ROS2의 실행인자 **</p>
</blockquote>
<ol>
<li>** --ros-args ** : ROS2 API와 관련된 옵션 변경이 가능하다</li>
<li>위 옵션이 붙지 않은 인자들 : 사용자 정의 실행 인자들이다.</li>
</ol>
<p>예를 들어 다음 명령어를 보자.</p>
<pre><code>$ ros2 run topic_service_action_rclcpp_example checker -g 100 --ros-args  -r __ns:=/demo</code></pre><p>여기서 -g 100으로 표기한 것은 사용자 정의 실행 인자이고,
--ros-args  -r __ns:=/demo 라고 표기한 것은 rclcpp::init 함수의 인자로 넘겨지게 된다.</p>
<p>여기서는 실행 인자 프로그래밍을 포함하고 있는 checker 노드의 코드를 통해서 설행해보자.</p>
<pre><code>#include &lt;cstdio&gt;
#include &lt;memory&gt;
#include &lt;string&gt;
#include &lt;utility&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;rcutils/cmdline_parser.h&quot;

#include &quot;checker/checker.hpp&quot;


void print_help()
{
  printf(&quot;For Node node:\n&quot;);
  printf(&quot;node_name [-h]\n&quot;);
  printf(&quot;Options:\n&quot;);
  printf(&quot;\t-h Help           : Print this help function.\n&quot;);
}

int main(int argc, char * argv[])
{
  if (rcutils_cli_option_exist(argv, argv + argc, &quot;-h&quot;)) {
    print_help();
    return 0;
  }

  rclcpp::init(argc, argv);

  float goal_total_sum = 50.0;
  char * cli_option = rcutils_cli_get_option(argv, argv + argc, &quot;-g&quot;);
  if (nullptr != cli_option) {
    goal_total_sum = std::stof(cli_option);
  }
  printf(&quot;goal_total_sum : %2.f\n&quot;, goal_total_sum);

  auto checker = std::make_shared&lt;Checker&gt;(goal_total_sum);

  rclcpp::spin(checker);

  rclcpp::shutdown();

  return 0;
}</code></pre><p>먼저 헤더 파일 부분에 실행 인자를 쉽게 확인 가능한 cmdline_parser가 포함되어 있는 것을 확인할 수 있다.</p>
<pre><code>#include &quot;rcutils/cmdline_parser.h&quot;</code></pre><p>그리고 main 함수 안을 보면 rcutils_cli_option_exist 함수를 이용하여 -h 인자가 있는지 확인한다. 만약 -h 인자가 있다면 print_help 함수를 호출하게 된다. 이는 노드에 대한 도움말을 출력하고 main() 함수를 종료하는 코드이다.</p>
<pre><code>  if (rcutils_cli_option_exist(argv, argv + argc, &quot;-h&quot;)) {
    print_help();
    return 0;
  }</code></pre><p>그리고</p>
<pre><code>rclcpp::init(argc, argv);</code></pre><p>이 코드를 통해 rclcpp가 --ros-args 인자를 확인 가능하도록 한다.</p>
<p>rcutils_cli_get_option 함수는 실행 인자를 확인하고, 그 값을 문자열 포인터로 반환해주는 역할을 한다. 해당 함수를 이용해 쉽게 여러 실행 인자를 parsing할 수 있고 문자열 포인터를 원하는 변수 타입으로 변경해 생성될 클래스의 인자로 넘겨줄 수 있다.</p>
<pre><code>  float goal_total_sum = 50.0;
  char * cli_option = rcutils_cli_get_option(argv, argv + argc, &quot;-g&quot;);
  if (nullptr != cli_option) {
    goal_total_sum = std::stof(cli_option);
  }
  printf(&quot;goal_total_sum : %2.f\n&quot;, goal_total_sum);

  auto checker = std::make_shared&lt;Checker&gt;(goal_total_sum);</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] C++ 패키지 설계 및 프로그래밍(토픽)]]></title>
            <link>https://velog.io/@hy_k/ROS2-C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%86%A0%ED%94%BD</link>
            <guid>https://velog.io/@hy_k/ROS2-C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%86%A0%ED%94%BD</guid>
            <pubDate>Sat, 21 Sep 2024 09:05:33 GMT</pubDate>
            <description><![CDATA[<p>이번에는 파이썬과 유사하게 기초 프로그래밍을 응용하여 C++ 패키지를 설계하고, 토픽 프로그래밍을 한번 해보자.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24798">https://cafe.naver.com/openrt/24798</a>
<a href="https://cafe.naver.com/openrt/24802">https://cafe.naver.com/openrt/24802</a></p>
<hr>
<h1 id="패키지-설계">패키지 설계</h1>
<p>이 부분의 경우 파이썬과 완전히 동일한 패키지를 만들 것이기 때문에, <a href="https://cafe.naver.com/openrt/24450">https://cafe.naver.com/openrt/24450</a>
혹은
<a href="https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%86%A0%ED%94%BD">https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%86%A0%ED%94%BD</a>
이곳을 참고하면 된다.</p>
<p>그리고 전체 코드를 보고 싶다면
<a href="https://github.com/robotpilot/ros2-seminar-examples/tree/main">https://github.com/robotpilot/ros2-seminar-examples/tree/main</a>
이곳에서 보면 된다.</p>
<p>만약, 별도로 파일을 만들지 않고 이곳에서 코드를 받아서 진행하려면 다음과 같이 수행하면 된다.</p>
<pre><code>$ cd robot_ws/src
$ git clone https://github.com/robotpilot/ros2-seminar-examples.git
$ cd ..
$ colcon build (혹은) colcon build --symlink-install
$ source install/local_setup.bash</code></pre><p>위 코드는 ROS2 Foxy에서 작성하신 코드기에 그보다 상위 버젼인 ROS2 Humble 혹은 Jazzy에서는 빌드가 되지 않을 수도 있다.</p>
<p>만약 빌드가 성공적으로 되었으면,</p>
<pre><code>$ ros2 run
혹은
$ ros2 launch</code></pre><p>명령어를 통해서 각 노드를 실행하면 된다.</p>
<hr>
<h3 id="패키지-설정-파일packagexml">패키지 설정 파일(package.xml)</h3>
<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;topic_service_action_rclcpp_example&lt;/name&gt;
  &lt;version&gt;0.2.0&lt;/version&gt;
  &lt;description&gt;ROS 2 rclcpp example package for the topic, service, action&lt;/description&gt;
  &lt;maintainer email=&quot;passionvirus@gmail.com&quot;&gt;Pyo&lt;/maintainer&gt;
  &lt;license&gt;Apache License 2.0&lt;/license&gt;
  &lt;author email=&quot;passionvirus@gmail.com&quot;&gt;Pyo&lt;/author&gt;
  &lt;author email=&quot;routiful@gmail.com&quot;&gt;Darby Lim&lt;/author&gt;

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

  &lt;depend&gt;rclcpp&lt;/depend&gt;
  &lt;depend&gt;rclcpp_action&lt;/depend&gt;
  &lt;depend&gt;msg_srv_action_interface_example&lt;/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><p>이전에 만들어놓은 인터페이스 패키지를 사용할 것이기 때문에 참고한다고 나와있다.</p>
<hr>
<h3 id="빌드-설정-파일cmakeliststxt">빌드 설정 파일(CMakeLists.txt)</h3>
<pre><code># Set minimum required version of cmake, project name and compile options
cmake_minimum_required(VERSION 3.8)
project(topic_service_action_rclcpp_example)

if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

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 dependencies
find_package(ament_cmake REQUIRED)
find_package(msg_srv_action_interface_example REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)

include_directories(include)

# Build
add_executable(argument src/arithmetic/argument.cpp)
ament_target_dependencies(argument
  msg_srv_action_interface_example
  rclcpp
)

add_executable(calculator src/calculator/main.cpp src/calculator/calculator.cpp)
ament_target_dependencies(calculator
  msg_srv_action_interface_example
  rclcpp
  rclcpp_action
)

add_executable(checker src/checker/main.cpp src/checker/checker.cpp)
ament_target_dependencies(checker
  msg_srv_action_interface_example
  rclcpp
  rclcpp_action
)

add_executable(operator src/arithmetic/operator.cpp)
ament_target_dependencies(operator
  msg_srv_action_interface_example
  rclcpp
)

# Install
install(TARGETS
  argument
  calculator
  checker
  operator
  DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY launch param
  DESTINATION share/${PROJECT_NAME}
)

# Test
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

# Macro for ament package
ament_package()</code></pre><p>크게 어려운 내용이 없어서 설명을 하지 않아도 될 것 같다.</p>
<hr>
<h1 id="토픽-프로그래밍">토픽 프로그래밍</h1>
<blockquote>
<p>💡 ** 토픽(topic) **
비동기식 단방향 메시지 송수신 방식,</p>
</blockquote>
<ul>
<li>토픽을 퍼블리시(publish) 하는 퍼블리셔(publisher)</li>
<li>토픽을 서브스크라이브(subscribe) 하는 서브스크라이버(subscriber)
이렇게 2가지로 구성된다.</li>
</ul>
<h3 id="퍼블리셔-노드">퍼블리셔 노드</h3>
<p>토픽 퍼블리셔 역할은 argument 노드가 수행한다.
include 폴더 안에 argument.hpp 파일을 작성하고, src 폴더 안에 argument.cpp 파일을 작성하여 헤더 파일과 구현 파일을 분리한다.</p>
<p>먼저 argument.hpp 파일이다.</p>
<pre><code>#ifndef ARITHMETIC__ARGUMENT_HPP_
#define ARITHMETIC__ARGUMENT_HPP_

#include &lt;chrono&gt;
#include &lt;memory&gt;
#include &lt;string&gt;
#include &lt;utility&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;

#include &quot;msg_srv_action_interface_example/msg/arithmetic_argument.hpp&quot;


class Argument : public rclcpp::Node
{
public:
  using ArithmeticArgument = msg_srv_action_interface_example::msg::ArithmeticArgument;

  explicit Argument(const rclcpp::NodeOptions &amp; node_options = rclcpp::NodeOptions());
  virtual ~Argument();

private:
  void publish_random_arithmetic_arguments();
  void update_parameter();

  float min_random_num_;
  float max_random_num_;

  rclcpp::Publisher&lt;ArithmeticArgument&gt;::SharedPtr arithmetic_argument_publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Subscription&lt;rcl_interfaces::msg::ParameterEvent&gt;::SharedPtr parameter_event_sub_;
  rclcpp::AsyncParametersClient::SharedPtr parameters_client_;
};
#endif  // ARITHMETIC__ARGUMENT_HPP_</code></pre><p>헤더 파일에 대해서 먼저 살펴보자.
우선, ROS2의 각종 필수 라이브러리들과 커스텀 헤더를 선언하였다.</p>
<blockquote>
<p>💡 ** 라이브러리 **</p>
</blockquote>
<ol>
<li>chrono : 시간을 다루는 라이브러리</li>
<li>memory : 동적 메모리와 스마트 포인터를 다루는 라이브러리</li>
<li>string : 문자열을 다루는 라이브러리</li>
<li>utility : 다양한 기능들을 담고 있는 라이브러리</li>
<li>rclcpp/rclcpp.hpp : 필수적인 rclcpp 라이브러리 헤더</li>
<li>커스텀 인터페이스 패키지의 헤더 파일<pre><code>#include &quot;msg_srv_action_interface_example/msg/arithmetic_argument.hpp&quot;</code></pre></li>
</ol>
<p>그리고 클래스에 대해서도 선언하였다.</p>
<blockquote>
<p>💡 ** Argument 클래스 **</p>
</blockquote>
<ol>
<li>생성자는 NodeOptions 객체를 인자로 받아, context, arguments, intro-process communication, parameter, allocator와 같은 다양한 옵션을 정할 수 있다.</li>
<li>스마트 포인터 타입의 멤버 변수 Publisher와 TimerBase가 선언되어있다. 이들은 콜백 함수 실행을 위한 타이머와 토픽 퍼블리셔 역할을 수행한다.</li>
</ol>
<hr>
<p>그 다음으로 argument.cpp 파일이다.</p>
<pre><code>#include &lt;cstdio&gt;
#include &lt;memory&gt;
#include &lt;string&gt;
#include &lt;utility&gt;
#include &lt;random&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;rcutils/cmdline_parser.h&quot;

#include &quot;arithmetic/argument.hpp&quot;

using namespace std::chrono_literals;

Argument::Argument(const rclcpp::NodeOptions &amp; node_options)
: Node(&quot;argument&quot;, node_options),
  min_random_num_(0.0),
  max_random_num_(0.0)
{
  this-&gt;declare_parameter(&quot;qos_depth&quot;, 10);
  int8_t qos_depth = this-&gt;get_parameter(&quot;qos_depth&quot;).get_value&lt;int8_t&gt;();
  this-&gt;declare_parameter(&quot;min_random_num&quot;, 0.0);
  min_random_num_ = this-&gt;get_parameter(&quot;min_random_num&quot;).get_value&lt;float&gt;();
  this-&gt;declare_parameter(&quot;max_random_num&quot;, 9.0);
  max_random_num_ = this-&gt;get_parameter(&quot;max_random_num&quot;).get_value&lt;float&gt;();
  this-&gt;update_parameter();

  const auto QOS_RKL10V =
    rclcpp::QoS(rclcpp::KeepLast(qos_depth)).reliable().durability_volatile();

  arithmetic_argument_publisher_ =
    this-&gt;create_publisher&lt;ArithmeticArgument&gt;(&quot;arithmetic_argument&quot;, QOS_RKL10V);

  timer_ =
    this-&gt;create_wall_timer(1s, std::bind(&amp;Argument::publish_random_arithmetic_arguments, this));
}

Argument::~Argument()
{
}

void Argument::publish_random_arithmetic_arguments()
{
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_real_distribution&lt;float&gt; distribution(min_random_num_, max_random_num_);

  msg_srv_action_interface_example::msg::ArithmeticArgument msg;
  msg.stamp = this-&gt;now();
  msg.argument_a = distribution(gen);
  msg.argument_b = distribution(gen);
  arithmetic_argument_publisher_-&gt;publish(msg);

  RCLCPP_INFO(this-&gt;get_logger(), &quot;Published argument_a %.2f&quot;, msg.argument_a);
  RCLCPP_INFO(this-&gt;get_logger(), &quot;Published argument_b %.2f&quot;, msg.argument_b);
}

void Argument::update_parameter()
{
  parameters_client_ = std::make_shared&lt;rclcpp::AsyncParametersClient&gt;(this);
  while (!parameters_client_-&gt;wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(this-&gt;get_logger(), &quot;Interrupted while waiting for the service. Exiting.&quot;);
      return;
    }
    RCLCPP_INFO(this-&gt;get_logger(), &quot;service not available, waiting again...&quot;);
  }

  auto param_event_callback =
    [this](const rcl_interfaces::msg::ParameterEvent::SharedPtr event) -&gt; void
    {
      for (auto &amp; changed_parameter : event-&gt;changed_parameters) {
        if (changed_parameter.name == &quot;min_random_num&quot;) {
          auto value = rclcpp::Parameter::from_parameter_msg(changed_parameter).as_double();
          min_random_num_ = value;
        } else if (changed_parameter.name == &quot;max_random_num&quot;) {
          auto value = rclcpp::Parameter::from_parameter_msg(changed_parameter).as_double();
          max_random_num_ = value;
        }
      }
    };

  parameter_event_sub_ = parameters_client_-&gt;on_parameter_event(param_event_callback);
}

void print_help()
{
  printf(&quot;For argument node:\n&quot;);
  printf(&quot;node_name [-h]\n&quot;);
  printf(&quot;Options:\n&quot;);
  printf(&quot;\t-h Help           : Print this help function.\n&quot;);
}

int main(int argc, char * argv[])
{
  if (rcutils_cli_option_exist(argv, argv + argc, &quot;-h&quot;)) {
    print_help();
    return 0;
  }

  rclcpp::init(argc, argv);

  auto argument = std::make_shared&lt;Argument&gt;();

  rclcpp::spin(argument);

  rclcpp::shutdown();

  return 0;
}</code></pre><p>먼저 hpp 파일에 없는 라이브러리를 추가적으로 include 하였다. 예를 들면 C언어 표준 입출력 라이브러리인 cstdio와 랜덤 정수 생성용 라이브러리인 random이다.</p>
<p>그리고 Argument 클래스 생성자에서는 노드 클래스를 활용해서 노드를 생성하고, 퍼블리셔 옵션을 지정하게 된다. 그리고 timer 변수를 통해서 멤버 함수를 콜백함수처럼 호출한다.</p>
<p>멤버 함수인 publish_random_arithmetic_arguments 멤버 함수는 timer에 의해 호출되어, 랜덤 숫자를 생성하고, 선언한 인터페이스를 통해서 인터페이스를 퍼블리시 한다. 그리고 로그를 작성한다.</p>
<hr>
<h3 id="서브스크라이브-코드">서브스크라이브 코드</h3>
<p>전체 코드가 굉장히 길기 때문에 토픽과 관련된 코드만 살펴본다.</p>
<pre><code>  arithmetic_argument_subscriber_ = this-&gt;create_subscription&lt;ArithmeticArgument&gt;(
    &quot;arithmetic_argument&quot;,
    QOS_RKL10V,
    [this](const ArithmeticArgument::SharedPtr msg) -&gt; void
    {
      argument_a_ = msg-&gt;argument_a;
      argument_b_ = msg-&gt;argument_b;

      RCLCPP_INFO(
        this-&gt;get_logger(),
        &quot;Timestamp of the message: sec %ld nanosec %ld&quot;,
        msg-&gt;stamp.sec,
        msg-&gt;stamp.nanosec);

      RCLCPP_INFO(this-&gt;get_logger(), &quot;Subscribed argument a: %.2f&quot;, argument_a_);
      RCLCPP_INFO(this-&gt;get_logger(), &quot;Subscribed argument b: %.2f&quot;, argument_b_);
    }
  );</code></pre><p>노드 클래스를 활용해서 생성자를 통해 calculator 노드로 초기화 하고, 이후 QoS 설정을 맞춰준 다음에 서브스크라이브 함수를 선언하였다. 콜백 함수는 서브스크라이브 한 토픽 메시지에 접근하여 멤버 변수에 저장하고, 이를 로그로 나타내고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] ROS2 프로그래밍 기초 - C++]]></title>
            <link>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B8%B0%EC%B4%88-C</link>
            <guid>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B8%B0%EC%B4%88-C</guid>
            <pubDate>Sat, 21 Sep 2024 03:25:35 GMT</pubDate>
            <description><![CDATA[<p>이번에는 C++ 기반 ROS2 프로그래밍의 기초에 대해서 알아보자.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24451">https://cafe.naver.com/openrt/24451</a></p>
<hr>
<h1 id="c-기반-ros2-프로그래밍-기초">C++ 기반 ROS2 프로그래밍 기초</h1>
<h3 id="패키지-생성">패키지 생성</h3>
<pre><code>$ ros2 pkg create my_first_ros_rclcpp_pkg --build-type ament_cmake --dependencies rclcpp std_msgs</code></pre><hr>
<h3 id="패키지-설정">패키지 설정</h3>
<h4 id="packagexml">package.xml</h4>
<p>rclcpp 패키지기 때문에 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_first_ros_rclcpp_pkg&lt;/name&gt;
  &lt;version&gt;0.1.0&lt;/version&gt;
  &lt;description&gt;TODO: Package description&lt;/description&gt;
  &lt;maintainer email=&quot;kimhoyun@todo.todo&quot;&gt;kimhoyun&lt;/maintainer&gt;
  &lt;license&gt;Apache 2.0&lt;/license&gt;

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

  &lt;depend&gt;rclcpp&lt;/depend&gt;
  &lt;depend&gt;std_msgs&lt;/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><hr>
<h4 id="cmakeliststxt">CMakeLists.txt</h4>
<p>이 파일은 의존성 패키지의 설정과 빌드 및 설치 관련 설정이다. 한 단어로 줄이면, 빌드 설정 파일이라고 할 수 있다.</p>
<pre><code>cmake_minimum_required(VERSION 3.8)
project(my_first_ros_rclcpp_pkg)

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

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

# build
add_executable(helloworld_publisher src/helloworld_publisher.cpp)
ament_target_dependencies(helloworld_publisher rclcpp std_msgs)

add_executable(helloworld_subscriber src/helloworld_subscriber.cpp)
ament_target_dependencies(helloworld_subscriber rclcpp std_msgs)

# install
install(TARGETS
  helloworld_publisher
  helloworld_subscriber
  DESTINATION lib/${PROJECT_NAME}
)

# test
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()</code></pre><hr>
<h3 id="퍼블리셔-노드-작성하기">퍼블리셔 노드 작성하기</h3>
<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;


class HelloworldPublisher : public rclcpp::Node{
public:
    HelloworldPublisher() : Node(&quot;helloworld_publisher&quot;), count_(0){
        auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
        helloworld_publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(
            &quot;helloworld&quot;, qos_profile
        );
        timer_ = this-&gt;create_wall_timer(
            1s, std::bind(&amp;HelloworldPublisher::publish_helloworld_msg, this));
    }
private:
    void publish_helloworld_msg(){
        auto msg = std_msgs::msg::String();
        msg.data = &quot;Hello World : &quot;+std::to_string(count_++);
        RCLCPP_INFO(this-&gt;get_logger(), &quot;Published Message: &#39;%s&#39;&quot;, msg.data.c_str());
        helloworld_publisher_-&gt;publish(msg);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher&lt;std_msgs::msg::String&gt;::SharedPtr helloworld_publisher_;
    size_t count_;
};

int main(int argc, char *argv[]){
    rclcpp::init(argc, argv);
    auto node = std::make_shared&lt;HelloworldPublisher&gt;();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}</code></pre><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>이 부분의 경우, std 계열의 헤더들을 우선적으로 선언하고 있다.</p>
<blockquote>
</blockquote>
<ol>
<li>** chrono ** : C++11부터 추가된 시간과 관련된 기능을 제공하는 헤더 파일</li>
<li>** functional ** : C++11부터 추가된 함수 객체를 다루기 위한 다양한 기능 제공, 람다 표현식, 표준 함수 객체(std::function)을 지원</li>
<li>** memory ** : 동적 메모리 관리를 위한 스마트 포인터 기능을 제공, std::shared_ptr, std::unique_ptr, std::weak_ptr과 같은 포인터 클래스가 포함됨</li>
<li>** string ** : C++ 표준 문자열 처리 라이브러리 기능 제공</li>
<li>rclcpp/rclcpp.hpp : ROS2의 핵심 라이브러리인 rclcpp 포함</li>
<li>string.hpp : std_msgs::msg의 String을 포함하는 헤더</li>
<li>using namespace std::chrono_literals; : C++14에 추가된 기능으로, std::chrono의 시간 리터럴(예: 100ms, 1s 등)을 코드에서 직접 사용할 수 있도록 해주는 기능</li>
</ol>
<pre><code>class HelloworldPublisher : public rclcpp::Node</code></pre><p>이 부분의 경우, Node 클래스를 상속해서 사용하겠다는 뜻이다.</p>
<pre><code>HelloworldPublisher() : Node(&quot;helloworld_publisher&quot;), count_(0){
        auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
        helloworld_publisher_ = this-&gt;create_publisher&lt;std_msgs::msg::String&gt;(
            &quot;helloworld&quot;, qos_profile
        );
        timer_ = this-&gt;create_wall_timer(
            1s, std::bind(&amp;HelloworldPublisher::publish_helloworld_msg, this));
    }</code></pre><p>여기 있는 생성자의 기능을 정리하면 다음과 같다.</p>
<blockquote>
</blockquote>
<ol>
<li>노드 생성자 호출 + 노드 이름 지정 + count_ 변수 초기화</li>
<li>퍼블리셔의 qoS 설정</li>
<li>create_publisher 함수를 통해 퍼블리셔 설정</li>
<li>create_wall_timer 함수를 이용해 주기를 1초로 지정한 콜백함수 실행</li>
</ol>
<pre><code>void publish_helloworld_msg(){
        auto msg = std_msgs::msg::String();
        msg.data = &quot;Hello World : &quot;+std::to_string(count_++);
        RCLCPP_INFO(this-&gt;get_logger(), &quot;Published Message: &#39;%s&#39;&quot;, msg.data.c_str());
        helloworld_publisher_-&gt;publish(msg);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher&lt;std_msgs::msg::String&gt;::SharedPtr helloworld_publisher_;
    size_t count_;</code></pre><p>이 부분의 경우 publish_helloworld_msg 콜백 함수이다. 내용을 정리하면 다음과 같다.</p>
<blockquote>
</blockquote>
<ol>
<li>퍼블리시 데이터 타입 및 실제 데이터 저장</li>
<li>로그를 통해 스크린에 표시</li>
<li>private 변수로 사용되는 각종 변수들을 선언</li>
</ol>
<blockquote>
<p>💡 ** 콜백 함수의 구현 **
콜백 함수 구현은 3가지 방식이 있다.</p>
</blockquote>
<ul>
<li>member function</li>
<li>lambda</li>
<li>local function
이 중에서 주로 member function과 lambda 방식을 사용하게 된다.</li>
</ul>
<pre><code>int main(int argc, char *argv[]){
    rclcpp::init(argc, argv);
    auto node = std::make_shared&lt;HelloworldPublisher&gt;();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}</code></pre><p>마지막으로 main 함수를 통해서 초기화 및 노드 생성, 노드 실행, 노드 소멸, 프로세스 종료를 지정하고 있다.</p>
<hr>
<h3 id="서브스크라이버-노드-작성">서브스크라이버 노드 작성</h3>
<pre><code>#include &lt;functional&gt;
#include &lt;memory&gt;

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

using std::placeholders::_1;

class HelloworldSubscriber : public rclcpp::Node
{
private:
    rclcpp::Subscription&lt;std_msgs::msg::String&gt;::SharedPtr helloworld_subscriber_;
    void subscribe_topic_message(const std_msgs::msg::String::SharedPtr msg) const{
        RCLCPP_INFO(this-&gt;get_logger(), &quot;Received message: &#39;%s&#39;&quot;, msg-&gt;data.c_str());
    }
public:
    HelloworldSubscriber() : Node(&quot;Helloworld_subscriber&quot;)
    {
        auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
        helloworld_subscriber_ = this-&gt;create_subscription&lt;std_msgs::msg::String&gt;(
            &quot;helloworld&quot;,
            qos_profile,
            std::bind(&amp;HelloworldSubscriber::subscribe_topic_message, this, _1)
        );
    }
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared&lt;HelloworldSubscriber&gt;();
    rclcpp::spin(node);
    rclcpp::shutdown();
}</code></pre><p>이것 역시 퍼블리셔 노드와 마찬가지로 하나씩 살펴보도록 하자.</p>
<pre><code>#include &lt;functional&gt;
#include &lt;memory&gt;

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

using std::placeholders::_1;</code></pre><p>여기서 주목할 부분은, bind 함수의 대체자 역할을 위해서 placeholders 클래스를 &quot;_1&quot;로 선언한 것이다.</p>
<pre><code>class HelloworldSubscriber : public rclcpp::Node
{
private:
    rclcpp::Subscription&lt;std_msgs::msg::String&gt;::SharedPtr helloworld_subscriber_;
    void subscribe_topic_message(const std_msgs::msg::String::SharedPtr msg) const{
        RCLCPP_INFO(this-&gt;get_logger(), &quot;Received message: &#39;%s&#39;&quot;, msg-&gt;data.c_str());
    }</code></pre><p>노드 클래스를 상속받아서 생성하고, 이후에 필요한 멤버 변수와 subscription 콜백 함수를 작성하였다.</p>
<pre><code>public:
    HelloworldSubscriber() : Node(&quot;Helloworld_subscriber&quot;)
    {
        auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
        helloworld_subscriber_ = this-&gt;create_subscription&lt;std_msgs::msg::String&gt;(
            &quot;helloworld&quot;,
            qos_profile,
            std::bind(&amp;HelloworldSubscriber::subscribe_topic_message, this, _1)
        );
    }</code></pre><p>이 부분은 생성자를 통해 부모 클래스의 생성자를 호출하고 노드 이름을 지정하였다.
이후 QoS 설정 및 서브스크라이브 설정을 수행하고, 콜백 함수를 지정하였다.</p>
<p>마지막으로 main 함수를 통해 노드를 실행하고 종료한다.</p>
<pre><code>int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared&lt;HelloworldSubscriber&gt;();
    rclcpp::spin(node);
    rclcpp::shutdown();
}</code></pre><hr>
<h3 id="빌드하기">빌드하기</h3>
<pre><code>$ colcon build # 전체 패키지 필드
$ colcon build --symlink-install --packages-select &lt;pkg_name&gt;
# 특정 패키지만 빌드
$ colcon build --symlink-install --packages-up-to &lt;pkg_name&gt;
# 의존성 패키지까지 한번에 빌드

$ source install/local_setup.bash</code></pre><hr>
<h3 id="실행하기">실행하기</h3>
<pre><code>$ ros2 run my_first_ros_rclcpp_pkg helloworld_publisher
$ ros2 run my_first_ros_rclcpp_pkg helloworld_subscriber</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] 파이썬 패키지 설계 및 프로그래밍(파라미터, 실행인자)]]></title>
            <link>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%8B%A4%ED%96%89%EC%9D%B8%EC%9E%90</link>
            <guid>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%8B%A4%ED%96%89%EC%9D%B8%EC%9E%90</guid>
            <pubDate>Fri, 20 Sep 2024 12:05:17 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 파이썬을 기반으로 하는 ROS2의 파라미터 및 실행인자 프로그래밍에 대해서 공부해보도록 하겠다.</p>
<p>참고한 링크 및 이미지 출처는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24690">https://cafe.naver.com/openrt/24690</a>
<a href="https://cafe.naver.com/openrt/24734">https://cafe.naver.com/openrt/24734</a></p>
<p>사용한 코드와 패키지는 모두 이전 포스팅과 이어진다.</p>
<hr>
<h1 id="파라미터-프로그래밍">파라미터 프로그래밍</h1>
<h3 id="파라미터">파라미터</h3>
<blockquote>
<p>💡 ** 파라미터 **
ROS2의 모든 노드는 파라미터 서버를 가지고 잇어서, 파라미터 클라이언트와의 서비스 통신을 통해서 파라미터에 접근이 가능하다. 이를 통해 ROS2의 파라미터는 일종의 글로벌 변수 역할을 통해 실시간으로 노드의 각종 값과 특성을 바꾸는 역할을 수행하는데 주로 사용된다.</p>
</blockquote>
<blockquote>
<p>💡 ** 파라미터와 서비스의 차이 **</p>
</blockquote>
<ul>
<li>파라미터는 특정 매개변수가 노드 내부 또는 외부에서 쉽게 읽고, 쓰고, 변경이 가능하도록 하는 것이다.</li>
<li>서비스는 요청(request)과 응답(response)로 구성된 동기식 양방향 데이터 통신이다.</li>
</ul>
<h3 id="파라미터-설정">파라미터 설정</h3>
<p>ROS2에서 파라미터를 사용하려면 다음 3가지 함수를 사용해야 한다.</p>
<blockquote>
<p>** 1. declare_parameter 함수**
노드에서 사용할 파라미터의 고유 이름을 지정하고 초깃값을 설정한다.</p>
</blockquote>
<blockquote>
<p>** 2. get_parameter 함수 **
노드에서 사용할 파라미터의 값을 불러오는 것으로, 선언된 파라미터의 고유 이름을 사용한다. 주로 .yaml 확장자를 가지는 파라미터 파일에 저장된 값을 불러오는데 사용한다.</p>
</blockquote>
<blockquote>
<p>** 3. add_on_set_parameters_callback 함수 **
서비스 형태로 파라미터 변경 요청이 있을 때 사용하는 함수로 지정한 콜백 함수를 호출한다.</p>
</blockquote>
<p>예시 코드를 살펴보면 다음과 같다.</p>
<pre><code>class Argument(Node):

    def __init__(self):
        super().__init__(&#39;argument&#39;)
        self.declare_parameter(&#39;qos_depth&#39;, 10)
        qos_depth = self.get_parameter(&#39;qos_depth&#39;).value
        self.declare_parameter(&#39;min_random_num&#39;, 0)
        self.min_random_num = self.get_parameter(&#39;min_random_num&#39;).value
        self.declare_parameter(&#39;max_random_num&#39;, 9)
        self.max_random_num = self.get_parameter(&#39;max_random_num&#39;).value
        self.add_on_set_parameters_callback(self.update_parameter)</code></pre><pre><code>    def update_parameter(self, params):
        for param in params:
            if param.name == &#39;min_random_num&#39; and param.type_ == Parameter.Type.INTEGER:
                self.min_random_num = param.value
            elif param.name == &#39;max_random_num&#39; and param.type_ == Parameter.Type.INTEGER:
                self.max_random_num = param.value
        return SetParametersResult(successful=True)</code></pre><p>위 예시 코드를 보면 declare_parameter 함수로 파라미터를 선언하고, get_paramter 함수를 통해 파라미터 초깃값을 지정하는 것을 볼 수 있다. 그리고 지정 콜백 함수인 update_parameter 함수를 통해 파라미터 변경 요청을 수행할 수 있다.</p>
<h3 id="cli-기반-파라미터-사용-방법">CLI 기반 파라미터 사용 방법</h3>
<p>먼저 실습을 위해 argument 노드를 실행하자.</p>
<pre><code>$ ros2 run topic_service_action_rclpy_example argument</code></pre><blockquote>
</blockquote>
<p>** 1. 파라미터 목록 확인 **</p>
<pre><code>$ ros2 param list</code></pre><p>현재 실행 중인 모든 노드의 모든 파라미터를 보여준다.</p>
<blockquote>
</blockquote>
<p>** 2. 현재 파라미터 값 확인 **</p>
<pre><code>$ ros2 param get &lt;node_name&gt; &lt;param_name&gt;</code></pre><p>예시를 들면 다음과 같다.</p>
<pre><code>$ ros2 param get /argument max_random_num</code></pre><p>이러면 결과로 9가 출력될 것이다.</p>
<blockquote>
</blockquote>
<p>** 3. 파라미터 값 변경 **</p>
<pre><code>$ ros2 param set &lt;node_name&gt; &lt;param_name&gt; &lt;value&gt;</code></pre><p>예를 들면 다음과 같다.</p>
<pre><code>$ ros2 param set /argument max_random_num 100</code></pre><p>이렇게 되면 max_random_num 파라미터가 100으로 변경된다.</p>
<h3 id="파라미터-사용-방법via-서비스-클라이언트">파라미터 사용 방법(via. 서비스 클라이언트)</h3>
<p>CLI 기반 말고도, 서비스 클라이언트를 통해서 서비스 요청으로 파라미터 값을 변경할 수 있는 방법도 있다. 이때 클라이언트 선언 부분 및 서비스를 요청하는 부분까지는 일반적인 서비스 클라이언트와 완전히 동일하다.</p>
<p>예시 코드를 보면 다음과 같다.</p>
<pre><code>from rcl_interfaces.msg import Parameter
from rcl_interfaces.msg import ParameterType
from rcl_interfaces.msg import ParameterValue
from rcl_interfaces.srv import SetParameters

(중략)

    self.random_num_parameter_client = self.create_client(
        SetParameters,
        &#39;argument/set_parameters&#39;)

(중략)
    def set_max_random_num_parameter(self, max_value):
        request = SetParameters.Request()
        parameter = ParameterValue(type=ParameterType.PARAMETER_INTEGER, integer_value=max_value)
        request.parameters = [Parameter(name=&#39;max_random_num&#39;, value=parameter)]
        service_client = self.random_num_parameter_client
        return self.call_service(service_client, request, &#39;max_random_num parameter&#39;)

    def call_service(self, service_client, request, service_name):
        wait_count = 1
        while not service_client.wait_for_service(timeout_sec=0.1):
            if wait_count &gt; 3:
                self.get_logger().warn(service_name + &#39; service is not available.&#39;)
                return False
            wait_count += 1
        service_client.call_async(request)
        return True</code></pre><h3 id="기본-파라미터-설정-방법">기본 파라미터 설정 방법</h3>
<p>많은 수의 파라미터를 한번에 선언(declare)하고, 읽고(get), 변경(set)하는 것은 직접 코드로 작성하기 어렵다. 이 때문에 ROS2에서는 YAML 형식으로 파라미터 파일을 작성하고, 이를 launch 파일에서 불러오도록 설정한다.</p>
<p>이를 위해서는 패키지 내부에 param 폴더를 만들고, 거기에 내가 원하는 yaml 파일을 만들어 넣으면 된다.</p>
<p>예시 yaml 파일은 다음과 같다.</p>
<pre><code>/**: # namespace and node name
  ros__parameters:
    qos_depth: 30
    min_random_num: 0
    max_random_num: 9</code></pre><p>/** 기호의 경우, 원래 저 위치에 namespace 및 node 이름을 작성해줘야 하나 이 부분은 변경의 가능성이 매우 크기 때문에 해당 기호를 통해서 간단히 생략하는 것이다.</p>
<p>이후 launch 파일에서 LaunchConfiguration 함수의 인자로 파라미터 파일의 경로를 저장하고, LaunchDescription 함수의 인자로 Node 함수를 이용해 parameter 인자에 앞서 yaml 파일의 경로를 지정하면 된다.</p>
<p>예시 코드는 다음과 같다.</p>
<pre><code>import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    param_dir = LaunchConfiguration(
        &#39;param_dir&#39;,
        default=os.path.join(
            get_package_share_directory(&#39;topic_service_action_rclpy_example&#39;),
            &#39;param&#39;,
            &#39;arithmetic_config.yaml&#39;))

    return LaunchDescription([
        DeclareLaunchArgument(
            &#39;param_dir&#39;,
            default_value=param_dir,
            description=&#39;Full path of parameter file&#39;),

        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;argument&#39;,
            name=&#39;argument&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),

        Node(
            package=&#39;topic_service_action_rclpy_example&#39;,
            executable=&#39;calculator&#39;,
            name=&#39;calculator&#39;,
            parameters=[param_dir],
            output=&#39;screen&#39;),
    ])</code></pre><p>파라미터 폴더의 경로를 읽어와 지정하고, 해당 파라미터를 노드 실행에 포함시키는 것을 확인할 수 있다.</p>
<p>그리고 이러한 launch.py와 yaml 파일은 setup.py 파일에도 꼭 넣어줘야 한다.
예시는 다음과 같다.</p>
<pre><code>        (share_dir + &#39;/launch&#39;, glob.glob(os.path.join(&#39;launch&#39;, &#39;*.launch.py&#39;))),
        (share_dir + &#39;/param&#39;, glob.glob(os.path.join(&#39;param&#39;, &#39;*.yaml&#39;))),</code></pre><hr>
<h1 id="실행-인자-프로그래밍">실행 인자 프로그래밍</h1>
<p>ROS2 프로그램 실행 시 옵션으로 인수를 추가하여 실행하는 경우가 있다 . 이때 사용하는 인자를 실행인자(argument)라고 하며, main 함수에서 매개변수를 통해 접근할 수 있다.</p>
<blockquote>
<p>💡 ** 파이썬의 실행인자 처리 **</p>
</blockquote>
<ul>
<li>인수들을 무시할 때는 args=None으로 하고 rclpy.init 함수에 넘긴다.</li>
<li>인자들을 사용하고자 한다면, argv(Argument Vector)에 매개변수를 저장하고 이를 rclpy.init 함수의 args 매개변수로 넘긴다.</li>
<li>이후 argparse 모듈을 이용해 실행 인자를 위한 구문 해석 프로그램을 작성한다. argparse 모듈은 파이썬 표준 parser 라이브러리이다.<pre><code>def main(argv=sys.argv[1:]):
  (argparse 구문)
  rclpy.init(args=argv)
  (이하 생략)</code></pre></li>
</ul>
<p>예시 코드를 통해서 한번 알아보자.</p>
<pre><code>import argparse
import sys

import rclpy

from topic_service_action_rclpy_example.checker.checker import Checker


def main(argv=sys.argv[1:]):
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        &#39;-g&#39;,
        &#39;--goal_total_sum&#39;,
        type=int,
        default=50,
        help=&#39;Target goal value of total sum&#39;)
    parser.add_argument(
        &#39;argv&#39;, nargs=argparse.REMAINDER,
        help=&#39;Pass arbitrary arguments to the executable&#39;)
    args = parser.parse_args()

    rclpy.init(args=args.argv)
    try:
        checker = Checker()
        checker.send_goal_total_sum(args.goal_total_sum)
        try:
            rclpy.spin(checker)
        except KeyboardInterrupt:
            checker.get_logger().info(&#39;Keyboard Interrupt (SIGINT)&#39;)
        finally:
            checker.arithmetic_action_client.destroy()
            checker.destroy_node()
    finally:
        rclpy.shutdown()


if __name__ == &#39;__main__&#39;:
    main()</code></pre><p>여기서 핵심은 다음과 같다.</p>
<ol>
<li>parser 만들기</li>
<li>인자 추가하기</li>
<li>인자 파싱하기</li>
<li>인자 사용하기</li>
</ol>
<h3 id="parser-만들기">parser 만들기</h3>
<p>이를 위해 필요한 것은 다음과 같다.</p>
<ul>
<li>argparse 모듈의 ArgumentParser 클래스를 이용한다.</li>
<li>위 코드에서는 parser 변수를 위 클래스를 이용해 선언한다.</li>
<li>여기서 formatter_class는 argparse 모듈의 가장 기본적 형식을 사용하도록 설정한다.<pre><code>def main(argv=sys.argv[1:]):
  parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpoFormatter)</code></pre></li>
</ul>
<h3 id="인자-추가하기">인자 추가하기</h3>
<p>실행 인자로 사용할 인자를 추가하려면 add_argument 함수를 호출하고, 해당 함수의 매개변수를 채우면 된다.</p>
<pre><code>parser.add_argument(
    &#39;-g&#39;, // 간략한 인자 이름
    &#39;--goal_total_sum&#39;, // 상세한 인자 이름
    type=int,
    default=50,
    help=&#39;Target Goal Value of Total Sum&#39;)</code></pre><p>마지막 설명을 통해 -h 혹은 --help 인자를 입력하였을 때 도움말을 출력할 수 있다.</p>
<h3 id="인자-parsing-하기">인자 parsing 하기</h3>
<p>인자 추가가 끝났다면, parse_args() 메서드를 통해 인자를 파싱하면 된다.</p>
<pre><code>args = parser.parser_args()</code></pre><h3 id="인자-사용하기">인자 사용하기</h3>
<p>인자에 접근하기 위해서는 인자를 파싱하여 대입한 args 변수를 사용해야 한다. 예를 들어 위에 코드를 볼 경우, add_argument를 통해 인자로 추가했던 &quot;--goal_total_sum&quot;은 &quot;args.goal_total_sum&quot;으로 접근이 가능하다.</p>
<pre><code>checker.send_goal_total_sum(args.goal_total_sum)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] string (문자열) 사용]]></title>
            <link>https://velog.io/@hy_k/C-string-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@hy_k/C-string-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Fri, 20 Sep 2024 07:53:40 GMT</pubDate>
            <description><![CDATA[<p>앞선 정리 포스팅에서 언급하였듯, C++에서는 문자열을 사용할 수 있는 방법이 2가지가 있다.</p>
<ul>
<li>cstring</li>
<li>string 클래스</li>
</ul>
<p>cstring은 null 문자(\0)로 끝나는, 전통적인 C언어의 문자열 처리 방식이고, string 클래스는 C++ 표준 라이브러리에서 제공하는 클래스로서 문자열을 객체로 다루게 된다. 이번 포스팅에서는 string에 대해서 알아보자.</p>
<hr>
<h2 id="string-클래스">string 클래스</h2>
<p>string 클래스를 사용하기 위해서는 다음 코드가 필요하다.</p>
<pre><code>#include &lt;string&gt;
using namespace std;</code></pre><p>string 클래스는 문자열의 크기에 맞추어 메모리를 동적으로 자동 조정하기 때문에 편리하게 사용할 수 있다. 이제부터 사용법에 대해서 알아보겠다.</p>
<h3 id="string-객체-생성-및-출력">string 객체 생성 및 출력</h3>
<pre><code>string str; // 빈 문자열 객체
string address(&quot;...&quot;); // 문자열로 초기화
string copyAddress(address); // 다른 문자열을 복사해서 생성
// 복사를 통한 생성은 cstring도 복사가 가능하다.</code></pre><p>생성은 이렇고, 출력은 다음과 같다.</p>
<pre><code>cout&lt;&lt;address&lt;&lt;endl;</code></pre><p>또한 객체이고, 메모리를 할당받아 생성하는 자료형이다보니 new와 delete 연산자를 통해서 동적으로 생성하고 반환할 수 있다.</p>
<pre><code>string *p = new string(&quot;C++&quot;);
cout&lt;&lt;*p;
p-&gt;append(&quot;Great!!&quot;);
cout&lt;&lt;*p;
delete p;</code></pre><blockquote>
<p>💡 ** string 클래스의 주요 생성자 **</p>
</blockquote>
<ol>
<li>string() : 빈 문자열 생성</li>
<li>string(const string&amp; str) : str을 복사한 새로운 string 객체 생성</li>
<li>string(const char* s) : cstring을 복사해서 생성</li>
<li>string(const char* s, int n) : 문자 배열 s에서 n개의 문자를 복사해서 생성</li>
</ol>
<h3 id="string-객체-문자열-입력">string 객체 문자열 입력</h3>
<p>cin 객체와 &gt;&gt; 연산자를 이용해서 키보드로부터 string 객체에 문자열을 입력받을 수 있다.</p>
<pre><code>string name;
cin &gt;&gt; name;</code></pre><p>앞서 살펴본 것처럼, &gt;&gt; 연산자는 공백문자를 포함하지 않는다. 따라서 공백문자를 포함해서 문자열을 입력하고 싶으면, getline() 함수를 사용해야 한다. getline() 함수는 string 헤더 파일에 선언되어 있다.</p>
<pre><code>string name;
getline(cin, name, &#39;\n&#39;);</code></pre><p>예시 코드를 한번 살펴보자.</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
    string names[5];
    for (int i = 0; i &lt; 5; i++) {
        cout &lt;&lt; &quot;이름 &gt;&gt; &quot;;
        getline(cin, names[i], &#39;\n&#39;);
    }

    string latter = names[0];
    for (int i = 0; i &lt; 5; i++) {
        if (latter &lt; names[i]) // latter가 names[i]보다 앞에 온다면
            latter = names[i];
    }    
    cout &lt;&lt; &quot;사전에서 가장 뒤에 나오는 문자열은 &quot; &lt;&lt; latter &lt;&lt; endl;
}</code></pre><h2 id="string-클래스를-통한-문자열-다루기">string 클래스를 통한 문자열 다루기</h2>
<h3 id="문자열-치환">문자열 치환</h3>
<p>간단히 = 연산자를 사용한다.</p>
<pre><code>string a = &quot;hello&quot;, b=&quot;world&quot;;
a = b; // a에 b의 문자열을 복사해서 치환</code></pre><h3 id="문자열-비교">문자열 비교</h3>
<p>문자열 비교는 compare() 함수를 사용한다. 이 함수는 문자열이 같으면 0을 반환하고, str보다 사전 순으로 앞에 오면 음수, 뒤에 오면 양수를 리턴한다. compare 함수의 원형은 다음과 같다.</p>
<pre><code>int compare(const string&amp; str);</code></pre><p>이 함수를 사용하는 예시는 다음과 같다.</p>
<pre><code>string name=&quot;hello&quot;;
string alias = &quot;kito&quot;;
int res = name.compare(alias);
if(res==0) cout &lt;&lt; &quot;두 문자열이 같다&quot; &lt;&lt; endl;
else if(res&lt;0) cout &lt;&lt; name &lt;&lt; &quot;&lt;&quot; &lt;&lt; alias &lt;&lt; endl;
else cout &lt;&lt; alias &lt;&lt; &quot;&lt;&quot; &lt;&lt; name &lt;&lt; endl;</code></pre><p>혹은 &quot;==&quot; 연산자를 통해서도 비교 연산이 가능하다.</p>
<h3 id="문자열-연결">문자열 연결</h3>
<p>문자열 연결은 연산자와 함수를 이용해서 가능하다.
함수의 경우, append() 함수를 사용한다. 원형은 다음과 같다.</p>
<pre><code>string&amp; append(const string&amp; str); // 문자열 뒤에 str 추가
string&amp; append(const string&amp; str, int pos, int n);
// str에서 pos 위치부터 n개 문자를 복사해서 추가</code></pre><p>혹은 +, += 연산자를 통해서도 할 수 있다.</p>
<pre><code>string a(&quot;hello world&quot;);
string b(&quot;!!!&quot;);
string c;
c = a+b; // hello world!!! 가 복사</code></pre><h3 id="문자열-삽입">문자열 삽입</h3>
<p>문자열 삽입은 insert() 함수와 replace() 함수를 통해서 진행할 수 있다.
insert() 함수의 원형은 다음과 같다.</p>
<pre><code>string&amp; insert(int pos, const string&amp; str);
// 문자열의 pos 위치에 str 삽입</code></pre><p>replace() 함수의 원형은 다음과 같다.</p>
<pre><code>string&amp; replace(int pos, int n, const string&amp; str);
// 문자열의 pos 위치부터 n개 문자를 str 문자열로 대치</code></pre><p>예시는 다음과 같다.</p>
<pre><code>string a(&quot;I love C++&quot;);
a.insert(2, &quot;really &quot;); // I really love C++로 변화

a.replace(2,11,&quot;study&quot;); // I study C++로 변화</code></pre><h3 id="문자열-길이">문자열 길이</h3>
<p>문자열 길이는 문자열에 포함된 문자 개수를 말하며, length()와 size() 함수를 통해서 문자열 길이를 리턴받을 수 있다. 길이와 달리, 객체의 내부 메모리 용량을 리턴하는 capacity() 함수도 있다.</p>
<p>각 함수들의 원형은 다음과 같다.</p>
<pre><code>int size();
int lengh();
int capacity();</code></pre><p>사용 예시는 다음과 같다.</p>
<pre><code>string a(&quot;abcdefghi&quot;);
int length = a.length();
int size = a.size();
int capa = a.capacity();</code></pre><h3 id="문자열-삭제">문자열 삭제</h3>
<p>erase()는 문자열의 일부분을 삭제할 수 있고, clear() 함수는 완저히 삭제가 가능하다.
함수의 원형은 다음과 같다.</p>
<pre><code>string&amp; erase(int pos, int n);
// pos부터 n개 문자 삭제

void clear(); // 모두 삭제하고 빈 문자열로 만들기</code></pre><h3 id="substring">substring</h3>
<p>substr() 함수를 사용하면 문자열에서 일부분만 발췌한 문자열(substring)을 얻을 수 있다. 이때 원 문자열은 변화가 없다.</p>
<pre><code>string substr(int pos, int n);
// pos위치부터 n개 문자를 새로운 substring으로 생성</code></pre><h3 id="문자열-내의-검색">문자열 내의 검색</h3>
<p>문자열 내에 특정 문자열이 존재하는지 검색하는 기능은, find() 함수를 통해서 구현된다. find() 함수는 문자열에서 특정 문자나 문자열을 발견하면 첫번째 인덱스를 리턴하며, 발견하지 못하면 -1을 리턴하게 된다.</p>
<p>먼저 원형을 살펴보자.</p>
<pre><code>int find(const string &amp; str);
// 문자열의 처음부터 str을 검색하여 발견한 처음 인덱스를 리턴, 없으면 -1 리턴

int find(const string&amp; str, int pos);
// 문자열의 pos 위치부터 str을 검색하여 발견한 처음 인덱스 리턴. 없으면 -1 리턴

int rfind(const string&amp; str, int pos);
// 문자열의 pos 위치부터 str을 검색하여 마지막에 발견한 인덱스 리턴, 없으면 -1 리턴</code></pre><p>예시를 들면 다음과 같다.</p>
<pre><code>string e = &quot;i love love C++&quot;;
int index = e.fine(&quot;love&quot;); // e에서 love를 검색해 인덱스 2 리턴
index = e.find(&quot;love&quot;, index+1); // e에서 3부터 love 검색, 7 리턴
index = e.find(&quot;C#&quot;); // -1 리턴
index = e.find(&#39;v&#39;, 7); // e의 인덱스 7부터 v를 검색, 9 리턴</code></pre><h3 id="문자열의-각-문자-다루기">문자열의 각 문자 다루기</h3>
<p>문자열은 인덱스 연산자인 &quot;[ ]&quot; 혹은 at() 함수를 통해서 문자열의 특정 위치에 있는 문자를 리턴할 수 있다. at() 함수의 원형은 다음과 같다.</p>
<pre><code>char &amp; at(int pos); // pos 위치의 문자 리턴</code></pre><p>&quot;[ ]&quot; 연산자를 사용하면 단순히 특정 위치의 문자를 리턴할 수 있는 것뿐만 아니라 해당 위치의 문자를 수정할 수 있다.</p>
<pre><code>a=&quot;1234&quot;;
a[1]=&quot;1&quot;;
// a는 1134</code></pre><h3 id="문자열의-숫자-변환">문자열의 숫자 변환</h3>
<p>C++11 표준부터 문자열을 숫자로 변환하는 전역 함수 stoi()를 추가하였다. stoi() 함수는 다음과 같이 사용한다.</p>
<pre><code>string year = &quot;2014&quot;;
int n = stoi(year); // n은 정수 2014의 값을 가짐</code></pre><h3 id="문자-다루기">문자 다루기</h3>
<p>string 헤더 파일은 문자열을 다루지 개별 문자를 다루는 기능은 없다. 문자를 다루는 함수는 &quot;locale&quot; 헤더 파일에 존재한다. &quot;locale&quot; 헤더 파일은 toupper() 함수, isdigit() 함수, isalpha() 함수 등을 지원한다.</p>
<blockquote>
</blockquote>
<p>toupper() 함수 : 대문자로 바꿔준다.
isdigit() 함수 : 숫자인지 판정해준다.
isalpha() 함수 : 알파벳인지 판정해준다.</p>
<h3 id="그-외-연산자">그 외 연산자</h3>
<blockquote>
</blockquote>
<ol>
<li>+= : 두 문자열을 연결해준다.</li>
<li>== : 같은 문자열이면 true 리턴</li>
<li>!= : 다른 문자열이면 true 리턴</li>
<li>s1 &lt; s2 : s1이 사전순으로 s2보다 앞에 오면 true 리턴</li>
<li>s1 &gt; s2 : s1이 사전순으로 s2보다 뒤에 오면 true 리턴</li>
<li>s1 &lt;= s2 : s1이 s2와 같거나 앞에 오면 true 리턴</li>
<li>s1 &gt;= s2 : s1이 s2와 같거나 뒤에 오면 true 리턴</li>
</ol>
<hr>
<h2 id="문자열을-다루는-예시-코드들">문자열을 다루는 예시 코드들</h2>
<h3 id="1-문자열-입력-받고-회전시키기">1. 문자열 입력 받고 회전시키기</h3>
<pre><code>#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
    string s;
    cout &lt;&lt; &quot;아래에 문자열을 입력하세요. 빈 칸이 있어도 됩니다(한글 안됨).&quot; &lt;&lt; endl;
    getline(cin, s, &#39;\n&#39;);
    int len = s.length();

    for (int i = 0; i &lt; len; i++) {
        string first = s.substr(0, 1); // 맨 앞에 문자 1개를 문자열로 분리
        string sub = s.substr(1, len - 1); // 나머지 문자열을 분리
        s = sub + first; // 두 문자열을 연결하여 새로운 문자열 생성
        cout &lt;&lt; s &lt;&lt; endl;
    }
}</code></pre><h3 id="2-덧셈-문자열을-입력받아-덧셈하기">2. 덧셈 문자열을 입력받아 덧셈하기</h3>
<pre><code>#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
    string s;
    cout &lt;&lt; &quot;덧셈이 포함된 문자열을 입력하세요&quot; &lt;&lt; endl;
    getline(cin, s, &#39;\n&#39;);

    int sum = 0;
    int startIndex = 0;
    while (true) {
        int fIndex = s.find(&#39;+&#39;, startIndex); // + 문자 검색
        if (fIndex == -1) {
            // 만약 +가 없다면
            string part = s.substr(startIndex);
            if (part == &quot;&quot;) break; //+로 끝나는 경우 중단
            cout &lt;&lt; part &lt;&lt; endl;
            sum += stoi(part); // 문자열을 수로 변환해서 더하기
            break;
        }
        int count = fIndex - startIndex; // 서브스트링으로 자를 문자 개수
        string part = s.substr(startIndex, count); //count개의 서브스트링 만들기

        cout &lt;&lt; part &lt;&lt; endl;
        sum += stoi(part); // 문자열을 수로 변환하여 더하기
        startIndex = fIndex + 1; // 검색을 시작할 인덱스 전진
    }
    cout &lt;&lt; &quot;숫자들의 합은 &quot; &lt;&lt; sum &lt;&lt; endl;
}</code></pre><h3 id="3-문자열-find-및-replace">3. 문자열 find 및 replace</h3>
<pre><code>#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
    string s;
    cout &lt;&lt; &quot;여러 줄의 문자열을 입력하세요. 입력의 끝은 &amp; 문자입니다.&quot; &lt;&lt; endl;
    getline(cin, s, &#39;&amp;&#39;);
    cin.ignore(); // &amp; 뒤에 따라오는 엔터 키 제거

    string f, r;
    cout &lt;&lt; endl &lt;&lt; &quot;find: &quot;;
    getline(cin, f, &#39;\n&#39;);
    // 검색할 문자열 입력
    cout &lt;&lt; &quot;replace : &quot;;
    getline(cin, r, &#39;\n&#39;); // 대치할 문자열 입력

    int startIndex = 0;
    while (true) {
        int fIndex = s.find(f, startIndex); // startIndex부터 f 검색
        if (fIndex == -1)
            break;
        // 문자열의 끝까지 변경하였다는 뜻
        s.replace(fIndex, f.length(), r); // fIndex부터 f의 길이만큼 문자열 r로 변경
        startIndex = fIndex + r.length();
    }
    cout &lt;&lt; s &lt;&lt; endl;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] 파이썬 패키지 설계 및 프로그래밍(토픽)]]></title>
            <link>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%86%A0%ED%94%BD</link>
            <guid>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EA%B3%84-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%ED%86%A0%ED%94%BD</guid>
            <pubDate>Fri, 20 Sep 2024 05:30:24 GMT</pubDate>
            <description><![CDATA[<p>이번에는 기초 프로그래밍을 응용해서, 토픽 / 서비스 / 액션 프로그래밍을 모두 이용하는 패키지를 설계하고 프로그래밍 해보자.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24637">https://cafe.naver.com/openrt/24637</a>
<a href="https://cafe.naver.com/openrt/24644">https://cafe.naver.com/openrt/24644</a>
<a href="https://cafe.naver.com/openrt/24662">https://cafe.naver.com/openrt/24662</a>
<a href="https://cafe.naver.com/openrt/24666">https://cafe.naver.com/openrt/24666</a>
<a href="https://cafe.naver.com/openrt/24690">https://cafe.naver.com/openrt/24690</a>
<a href="https://cafe.naver.com/openrt/24734">https://cafe.naver.com/openrt/24734</a></p>
<hr>
<h1 id="패키지-설계">패키지 설계</h1>
<p><img src="https://velog.velcdn.com/images/hy_k/post/5ed51782-c355-4aa9-807b-edc545a57559/image.png" alt="">
위 이미지대로 작동하는 패키지를 한번 만들어볼 것이다.</p>
<ol>
<li>argument node</li>
</ol>
<p>-&gt; arithmetic_argument 토픽으로 현재 시간, 변수 a,b 퍼블리시
2. calculator node
-&gt; arithmetic_argument 토픽이 생성한 시간과 변수를 서브스크라이브
3. operator node
-&gt; arithmetic_operator 서비스를 통해 calculator 노드에게 연산자를 요청
4. calculator node
-&gt; 저장하고 잇는 변수 a와 b를 서비스 요청으로 받은 연산자를 이용하여 연산하고, 이후 결과값을 응답으로 보낸다
-&gt; 액션 목표값을 전달받은 이후부터는 연산 결과값을 누적하고 액션 피드백으로 연산식을 보낸다. 누적된 연산 결과값이 전달받은 목표값보다 커지면 액션 결과값으로 누적된 연산 결과값과 연산식 모두를 보낸다.
5. checker node
-&gt; 연산 결과값의 누적 한계치를 액션 목표값으로 전달</p>
<p>** 전체 패키지 구조와 코드 ** 는
<a href="https://github.com/robotpilot/ros2-seminar-examples/tree/main/topic_service_action_rclpy_example">https://github.com/robotpilot/ros2-seminar-examples/tree/main/topic_service_action_rclpy_example</a>
이곳을 참조하도록 하자.</p>
<h3 id="package-생성">package 생성</h3>
<pre><code>$ cd robot_ws/src
$ ros2 pkg create topic_service_action_rclpy_example --build-type ament_python --dependencies rclpy std_msgs</code></pre><h3 id="packagexml">package.xml</h3>
<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;topic_service_action_rclpy_example&lt;/name&gt;
  &lt;version&gt;0.1.0&lt;/version&gt;
  &lt;description&gt;TODO: Package description&lt;/description&gt;
  &lt;maintainer email=&quot;kimhoyun@todo.todo&quot;&gt;kimhoyun&lt;/maintainer&gt;
  &lt;license&gt;TODO: License declaration&lt;/license&gt;

  &lt;depend&gt;rclpy&lt;/depend&gt;
  &lt;depend&gt;std_msgs&lt;/depend&gt;
  &lt;depend&gt;msg_srv_action_interface_example&lt;/depend&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><h3 id="setuppy">setup.py</h3>
<pre><code>#!/usr/bin/env python3
# 쉐뱅을 통해 파이썬 인터프리터 지정

from setuptools import find_packages, setup
from setuptools import setup

import glob # 파일 경로를 통해 특정 확장자 파일을 찾을 때 유용
import os # 운영체제와 상호작용하기 위한 함수 제공

package_name = &#39;topic_service_action_rclpy_example&#39;
share_dir = &#39;share/&#39; + package_name

setup(
    name=package_name,
    version=&#39;0.0.0&#39;,
    packages=find_packages(exclude=[&#39;test&#39;]),
    data_files=[
        (&#39;share/ament_index/resource_index/packages&#39;,
            [&#39;resource/&#39; + package_name]),
        (&#39;share/&#39; + package_name, [&#39;package.xml&#39;]),
        (share_dir + &#39;/launch&#39;, glob.glob(os.path.join(&#39;launch&#39;, &#39;*.launch.py&#39;))),
        (share_dir + &#39;/param&#39;, glob.glob(os.path.join(&#39;param&#39;, &#39;*.yaml&#39;))),
    ],
    install_requires=[&#39;setuptools&#39;],
    zip_safe=True,
    maintainer=&#39;kimhoyun&#39;,
    maintainer_email=&#39;kimhoyun@todo.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;argument = topic_service_action_rclpy_example.arithmetic.argument:main&#39;,
            &#39;operator = topic_service_action_rclpy_example.arithmetic.operator:main&#39;,
            &#39;calculator = topic_service_action_rclpy_example.calculator.main:main&#39;,
            &#39;checker = topic_service_action_rclpy_example.checker.main:main&#39;,
        ],
    },
)</code></pre><hr>
<h3 id="파이썬-토픽-프로그래밍">파이썬 토픽 프로그래밍</h3>
<p>토픽은 단방향, 비동기식 메시지 송수신 방식으로, 토픽을 퍼블리시 하는 퍼블리셔와 서브스크라이브 하는 서브스크라이버로 구성된다.</p>
<p>전체 코드는 위에 있는 깃헙 링크 내에 들어가면 있으니, 그것을 보면 될 것이고 여기서는 토픽에 관련된 것만 살펴보겠다.</p>
<ul>
<li>** 퍼블리셔 코드 **</li>
<li>topic_service_action_rclpy_example/topic_service_action_rclpy_example/arithmetic/argument.py<pre><code>import random
</code></pre></li>
</ul>
<p>from msg_srv_action_interface_example.msg import ArithmeticArgument
from rcl_interfaces.msg import SetParametersResult
import rclpy
from rclpy.node import Node
from rclpy.parameter import Parameter
from rclpy.qos import QoSDurabilityPolicy
from rclpy.qos import QoSHistoryPolicy
from rclpy.qos import QoSProfile
from rclpy.qos import QoSReliabilityPolicy</p>
<p>class Argument(Node):</p>
<pre><code>def __init__(self):
    super().__init__(&#39;argument&#39;)
(중략)

    QOS_RKL10V = QoSProfile(
        reliability=QoSReliabilityPolicy.RELIABLE,
        history=QoSHistoryPolicy.KEEP_LAST,
        depth=qos_depth,
        durability=QoSDurabilityPolicy.VOLATILE)

    self.arithmetic_argument_publisher = self.create_publisher(
        ArithmeticArgument,
        &#39;arithmetic_argument&#39;,
        QOS_RKL10V)

    self.timer = self.create_timer(1.0, self.publish_random_arithmetic_arguments)

    (중략)

def publish_random_arithmetic_arguments(self):
    msg = ArithmeticArgument()
    msg.stamp = self.get_clock().now().to_msg()
    msg.argument_a = float(random.randint(self.min_random_num, self.max_random_num))
    msg.argument_b = float(random.randint(self.min_random_num, self.max_random_num))
    self.arithmetic_argument_publisher.publish(msg)
    self.get_logger().info(&#39;Published argument a: {0}&#39;.format(msg.argument_a))
    self.get_logger().info(&#39;Published argument b: {0}&#39;.format(msg.argument_b))</code></pre><pre><code>QoSProfile 클래스를 통해서 토픽 퍼블리셔가 사용할 QoS 설정을 하는 것을 볼 수 있다. 또한 퍼블리셔를 선언해 어떤 인터페이스를 사용할 것인지, 어떤 이름으로 발행할 것인지, 그리고 콜백 함수는 얼마마다 호출할 것인지 등도 나타내고 있다.

그리고 아래 ** def publish_random_arithmetic_argument(self) 콜백함수를 통해서 현재 시간과 랜덤 실수로 정해지는 변수를 기입해 토픽으로 발행함을 알 수 있다.

- ** 서브스크라이버 코드 **
- topic_service_action_rclpy_example/topic_service_action_rclpy_example/calculatopr/calculator.py</code></pre><p>class Calculator(Node):</p>
<pre><code>def __init__(self):
    super().__init__(&#39;calculator&#39;)
    self.argument_a = 0.0
    self.argument_b = 0.0
    self.callback_group = ReentrantCallbackGroup()

    (일부 코드 생략)

    QOS_RKL10V = QoSProfile(
        reliability=QoSReliabilityPolicy.RELIABLE,
        history=QoSHistoryPolicy.KEEP_LAST,
        depth=qos_depth,
        durability=QoSDurabilityPolicy.VOLATILE)

    self.arithmetic_argument_subscriber = self.create_subscription(
        ArithmeticArgument,
        &#39;arithmetic_argument&#39;,
        self.get_arithmetic_argument,
        QOS_RKL10V,
        callback_group=self.callback_group)

def get_arithmetic_argument(self, msg):
    self.argument_a = msg.argument_a
    self.argument_b = msg.argument_b
    self.get_logger().info(&#39;Subscribed at: {0}&#39;.format(msg.stamp))
    self.get_logger().info(&#39;Subscribed argument a: {0}&#39;.format(self.argument_a))
    self.get_logger().info(&#39;Subscribed argument b: {0}&#39;.format(self.argument_b))</code></pre><pre><code>전체 코드 중에서 서브스크라이버에 해당하는 부분만 간추려놓았다.

QoS 설정을 똑같이 맞춰주고, 서브스크라이버를 선언하고 동일한 인터페이스, 동일한 토픽 이름 그리고 서브스크라이브 콜백 함수와 QoS 프로필을 선언한 것을 알 수 있다.

그리고 콜백함수에서는 변수들을 저장하고, 로깅을 통해 토픽을 받은 시간과 변수들을 화면에 출력하도록 지시하였다.

위 퍼블리셔와 서브스크라이버는 다음과 같이 실행이 가능하다.</code></pre><p>$ ros2 run topic_service_action_rclpy_example calculator
$ ros2 run topic_service_action_rclpy_example argument</p>
<pre><code>이 부분은
- topic_service_action_rclpy_example/setup.py

위 파일에 위치한 entry_points를 보면 실행 노드로서 알 수 있다.</code></pre><pre><code>entry_points={
    &#39;console_scripts&#39;: [
        &#39;argument = topic_service_action_rclpy_example.arithmetic.argument:main&#39;,
        &#39;operator = topic_service_action_rclpy_example.arithmetic.operator:main&#39;,
        &#39;calculator = topic_service_action_rclpy_example.calculator.main:main&#39;,
        &#39;checker = topic_service_action_rclpy_example.checker.main:main&#39;,
    ],
},</code></pre><pre><code>한가지 특이할만한 점에라면, calculator 노드에 병렬 스레드를 설정했다는 것이다.
</code></pre><p>import rclpy
from rclpy.executors import MultiThreadedExecutor</p>
<p>from topic_service_action_rclpy_example.calculator.calculator import Calculator</p>
<p>def main(args=None):
    rclpy.init(args=args)
    try:
        calculator = Calculator()
        executor = MultiThreadedExecutor(num_threads=4)
        executor.add_node(calculator)
        try:
            executor.spin()
        except KeyboardInterrupt:
            calculator.get_logger().info(&#39;Keyboard Interrupt (SIGINT)&#39;)
        finally:
            executor.shutdown()
            calculator.arithmetic_action_server.destroy()
            calculator.destroy_node()
    finally:
        rclpy.shutdown()</p>
<p>if <strong>name</strong> == &#39;<strong>main</strong>&#39;:
    main()</p>
<pre><code>이 부분은 4개의 스레드를 사용하는 Executor를 생성함으로써 토픽 퍼블리셔의 콜백 함수, 서비스 서버의 콜백 삼후, 액션 서버의 콜백 함수, 특정 타이머의 콜백 함수 등을 병렬 처리하도록 설정한 것이라고 보면된다.
***</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] ROS2 프로그래밍 기초 - 파이썬]]></title>
            <link>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B8%B0%EC%B4%88-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B8%B0%EC%B4%88-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Fri, 20 Sep 2024 01:15:49 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 파이썬 프로그래밍 기초에 대해서 알아보자.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24450">https://cafe.naver.com/openrt/24450</a></p>
<hr>
<h1 id="패키지-생성">패키지 생성</h1>
<pre><code>$ cd robot_ws/src
$ ros2 pkg create my_first_ros_rclpy_pkg --build-type ament_python --dependencies rclpy std_msgs</code></pre><p>그리고 package.xml과 setup.py, setup.cfg 파일을 수정해야 한다.</p>
<h3 id="packagexml">package.xml</h3>
<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_first_ros_rclpy_pkg&lt;/name&gt;
  &lt;version&gt;0.0.0&lt;/version&gt;
  &lt;description&gt;basic ROS python programming pkg&lt;/description&gt;
  &lt;maintainer email=&quot;alpha12334@naver.com&quot;&gt;kimhoyun&lt;/maintainer&gt;
  &lt;license&gt;Apache 2.0&lt;/license&gt;

  &lt;depend&gt;rclpy&lt;/depend&gt;
  &lt;depend&gt;std_msgs&lt;/depend&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><h3 id="setuppy">setup.py</h3>
<p>entry_points 옵션의 console_scripts를 사용해서 실행 파일을 설정하는 것이 setup.py에서 가장 핵심이다. 여기에 내가 실행하고자 하는 파이썬 노드 이름을 입력하면 된다.</p>
<pre><code>from setuptools import find_packages, setup
from setuptools import setup

package_name = &#39;my_first_ros_rclpy_pkg&#39;

setup(
    name=package_name,
    version=&#39;0.1.0&#39;,
    packages=find_packages(exclude=[&#39;test&#39;]),
    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;kimhoyun&#39;,
    maintainer_email=&#39;kimhoyun@todo.todo&#39;,
    description=&#39;TODO: Package description&#39;,
    license=&#39;Apache License 2.0&#39;,
    tests_require=[&#39;pytest&#39;],
    entry_points={
        &#39;console_scripts&#39;: [
            &quot;helloworld_publisher = my_first_ros_rclpy_pkg.helloworld_publisher:main&quot;,
            &quot;helloworld_subscriber = my_first_ros_rclpy_pkg.helloworld_subscriber:main&quot;,
        ],
    },
)</code></pre><h3 id="setupcfg">setup.cfg</h3>
<p>기본적으로 작성이 되어있으며,</p>
<ol>
<li>패키지 이름을 기재해야 하는 것</li>
<li>지정 폴더에 실행 파일이 생성된다는 점
이 2가지만 기억하면 된다.<pre><code>[develop]
script_dir=$base/lib/my_first_ros_rclpy_pkg
[install]
install_scripts=$base/lib/my_first_ros_rclpy_pkg</code></pre></li>
</ol>
<h3 id="퍼블리셔-노드-작성">퍼블리셔 노드 작성</h3>
<pre><code>import rclpy
from rclpy.node import Node # rclpy로부터 Node 클래스를 사용
from rclpy.qos import QoSProfile # QoS 설정
from std_msgs.msg import String # std_msgs의 String 인터페이스 사용

class HelloworldPublisher(Node): # 노드 클래스 상속
    def __init__(self):
        super().__init__(&quot;helloworld_publisher&quot;) # 부모 클래스 생성자 호출 -&gt; 노드 이름 지정
        qos_profile = QoSProfile(depth=10) # qos 설정 커스텀
        self.helloworld_publisher = self.create_publisher(String, &quot;helloworld&quot;, qos_profile)
        # 토픽 퍼블리셔 설정 -&gt; String 타입의 helloworld 토픽을 qos_profile 따라 발행
        self.timer = self.create_timer(1, self.publish_helloworld_msg)
        # 타이머 함수를 통해서 콜백 함수를 지속적으로 실행, 1초마다 실행을 의미
        self.count = 0

    def publish_helloword_msg(self): # 콜백 함수
        msg = String() # string 타입 지정
        msg.data = &quot;Hello world : {0}&quot;.format(self.count) # 원하는 메시지 넣기
        self.helloworld_publisher.publish(msg) # 토픽 발행
        self.get_logger().info(&quot;Published Message : {0}&quot;.format(msg.data))
        # ROS2의 로깅 방식
        self.count += 1

def main(args=None):
    rclpy.init(args=args) # rclpy.init 함수를 통해서 초기화
    node = HelloworldPublisher() # node 변수를 통해서 Node 생성
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info(&quot;Keyboard Interrupt (SIGINT)&quot;)
        # Ctrl + C 입력 등에서는 노드가 종료하게 만듦
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__==&quot;__main__&quot;:
    main()</code></pre><h3 id="서브스크라이버-노드">서브스크라이버 노드</h3>
<pre><code>import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String # 퍼블리셔 노드와 동일

class HelloworldSubscirber(Node):
    def __init__(self):
        super().__init__(&quot;Helloworld_subscriber&quot;)
        qos_profile = QoSProfile(depth=10)
        self.helloworld_subscriber = self.create_subscription(
            # 토픽 서브스크라이브 정의
            String,
            &quot;helloworld&quot;,
            self.subscirbe_topic_message, # 콜백 함수 실행
            qos_profile
        )

    def subscirbe_topic_message(self, msg): # 콜백 함수 선언 및 구현
        self.get_logger().info(&quot;Received Message : {0}&quot;.format(msg.data))

def main(args=None):
    rclpy.init(args=args)
    node = HelloworldSubscirber()

    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info(&quot;Keyboard Interrupt (SIGINT)&quot;)
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == &quot;__main__&quot;:
    main()
</code></pre><h3 id="빌드하기">빌드하기</h3>
<pre><code>$ cd robot_ws
$ colcon build --symlink-install --packages-select my_first_ros_rclpy_pkg</code></pre><h3 id="실행하기">실행하기</h3>
<pre><code>$ source install/local_setup.bash
// 혹은
$ source install/setup.bash
// 그 다음에
$ ros2 run my_first_ros_rclpy_pkg hellworld_publisher

// 다른 터미널에서는
$ cd robot_ws &amp;&amp; source install/setup.bash
$ ros2 run my_first_ros_rclpy_pkg helloworld_subscirber</code></pre><p>이상으로 가장 기초적인 ROS2 파이썬 프로그래밍에 대해서 알아보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] ROS2 프로그래밍 기초 - 인터페이스 패키지 만들기]]></title>
            <link>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B8%B0%EC%B4%88-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hy_k/ROS2-ROS2-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B8%B0%EC%B4%88-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Fri, 20 Sep 2024 00:35:11 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅부터는 ROS2 프로그래밍을 본격적으로 해보자. 우선, 가장 기초가 되는 부분인 인터페이스 패키지 만들기부터 시작을 하겠다.</p>
<p>참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24629">https://cafe.naver.com/openrt/24629</a></p>
<hr>
<h1 id="인터페이스">인터페이스</h1>
<blockquote>
<p>💡 ** 인터페이스 **
ROS2에서는 데이터를 주고 받기 위한 메시지 통신(토픽, 서비스, 액션)을 하는데 이에 메시지 통신을 위해서 사용하는 수단을 인터페이스라고 한다. 기본적인 자료형 및 간단한 자료구조, 배열 등을 사용한다.</p>
</blockquote>
<ul>
<li>토픽: msg 인터페이스</li>
<li>서비스: srv 인터페이스</li>
<li>액션: action 인터페이스
위와 같이 사용하게 된다.</li>
</ul>
<p>만약 이미 존재하는 인터페이스(예 - sensor_msgs/msg/PointCloud2)로 내가 담고자 하는 데이터를 모두 담아내지 못한다면, 커스텀 인터페이스를 작성해야 한다. 그리고 커스텀 인터페이스를 작성할 때 의존성 측면을 고려한다면 별도의 인터페이스 패키지를 작성하는 것이 훨씬 간편하다. 이러한 맥락에서 이번 포스팅에서는 인터페이스 패키지를 작성할 것이다.</p>
<hr>
<h1 id="인터페이스-패키지-만들기">인터페이스 패키지 만들기</h1>
<p>msg, srv, action 인터페이스에 따른 구조는 이전 포스팅(&quot;파라미터와 인터페이스&quot;)을 참고하면 된다.</p>
<p>우선 패키지를 생성한다.</p>
<pre><code>$ cd robot_ws/src
$ ros2 pkg create --build-type ament_cmake msg_srv_action_interface_example</code></pre><p>그리고 mkdir 명령어 혹은 텍스트 에디터를 통해 패키지 내에 msg, srv, action 폴더를 생성해주자. 또, 다음과 같은 구조로 파일을 생성하자.</p>
<pre><code>├── action
│   └── ArithmeticChecker.action
├── msg
│   └── ArithmeticArgument.msg
└── srv
    └── ArithmeticOperator.srv</code></pre><blockquote>
<p>.msg, .srv, .action 파일들은 .h(pp) 파일로 변환한 후 인터페이스 타입으로 사용된다.</p>
</blockquote>
<p>각각 파일들은 다음과 같이 작성한다.</p>
<h3 id="arithmeticargumentmsg">ArithmeticArgument.msg</h3>
<pre><code>builtin_interfaces/Time stamp
float32 argument_a
float32 argument_b</code></pre><h3 id="arithmeticoperatorsrv">ArithmeticOperator.srv</h3>
<pre><code># consts
int8 PLUS = 1
int8 MINUS = 2
int8 MULTIPLY = 3
int8 DIVISION = 4

# Request
int8 arithmetic_operator
---
# Response
float32 arithmetic_result</code></pre><h3 id="arithmeticcheckeraction">ArithmeticChecker.action</h3>
<pre><code># Goal
float32 goal_sum
---
# Result
string[] all_formula
float32 total_sum
---
# Feedback
string[] formula</code></pre><p>딱 보면 각 인터페이스 별 구조의 차이가 보일 것이다.</p>
<p>그리고 package.xml 파일과 CMakeLists.txt 파일을 수정하자. 인터페이스 패키지는 순수 파이썬 패키지가 아니기 때문에 setup.py가 아니라 CMakeLists.txt 파일을 수정해야 한다.</p>
<h3 id="packagexml">package.xml</h3>
<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;msg_srv_action_interface_example&lt;/name&gt;
  &lt;version&gt;0.1.0&lt;/version&gt;
  &lt;description&gt;basic interface pkg&lt;/description&gt;
  &lt;maintainer email=&quot;suberkut76@gmail.com&quot;&gt;kimhoyun&lt;/maintainer&gt;
  &lt;license&gt;Apache 2.0&lt;/license&gt;

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

  &lt;exec_depend&gt;builtin_interfaces&lt;/exec_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;

  &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><p>인터페이스 패키지를 생성할 때, 빌드 시에는 DDS에서 사용하는 IDL(Interface Definition Language) 생성과 관련된 ** rosidl_default_generators ** 가 사용된다는 점, 실행 시에는 ** builtin_interfaces ** 와 ** rosidl_default_runtime ** 이 사용된다는 점을 반드시 기억해야 한다.</p>
<p>이는 인터페이스 패키지를 작성할 때 반드시 필요한 의존성 패키지기 때문이다.</p>
<h3 id="cmakeliststxt">CMakeLists.txt</h3>
<pre><code>cmake_minimum_required(VERSION 3.8)
project(msg_srv_action_interface_example)

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 dependencies
find_package(ament_cmake REQUIRED)
find_package(builtin_interfaces REQUIRED)
find_package(rosidl_default_generators REQUIRED)

set(msg_files
  &quot;msg/ArithmeticArgument.msg&quot;
)
set(srv_files
  &quot;srv/ArithmeticOperator.srv&quot;
)
set(action_files
  &quot;action/ArithmeticChecker.action&quot;
)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  ${srv_files}
  ${action_files}
  DEPENDENCIES builtin_interfaces
)

ament_export_dependencies(rosidl_default_runtime)
ament_package()</code></pre><p>일반적인 CMakeLists.txt 파일과 다르게 set 명렁어를 통해서 msg, srv, action 파일을 지정하고, rosidl_generate_interfaces에 해당 set들을 기입하면 된다.</p>
<h3 id="빌드하기">빌드하기</h3>
<pre><code>$ cd .. (혹은 cd robot_ws)
$ colcon build --symlink-install --packages-select msg_srv_action_interface_example</code></pre><p>이를 통해서 빌드를 하고, 성공적으로 실행 파일을 생성할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] ROS2의 패키지 파일]]></title>
            <link>https://velog.io/@hy_k/ROS2-ROS2%EC%9D%98-%ED%8C%A8%ED%82%A4%EC%A7%80-%ED%8C%8C%EC%9D%BC</link>
            <guid>https://velog.io/@hy_k/ROS2-ROS2%EC%9D%98-%ED%8C%A8%ED%82%A4%EC%A7%80-%ED%8C%8C%EC%9D%BC</guid>
            <pubDate>Thu, 19 Sep 2024 00:58:08 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 패키지 내부의 파일들에 대해서 다뤄보겠다.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24422">https://cafe.naver.com/openrt/24422</a></p>
<hr>
<h1 id="패키지-파일">패키지 파일</h1>
<blockquote>
<p>💡 ** 패키지 파일(Package File) **</p>
</blockquote>
<ul>
<li>패키지 설정 파일(package.xml)</li>
<li>빌드 설정 파일(CMakeLists.txt)</li>
<li>파이썬 패키지 설정 파일(setup.py)</li>
<li>파이썬 패키지 환경설정 파일(setup.cfg)</li>
<li>RQt 플러그인 설정 파일(plugin.xml)</li>
<li>패키지 변경로그 파일(CHANGELOG.rst)</li>
<li>라이선스 파일(LICENSE)</li>
<li>패키지 설명 파일(README.md)</li>
</ul>
<h3 id="패키지-설정-파일packagexml">패키지 설정 파일(package.xml)</h3>
<ul>
<li>패키지 정보를 기술하는 파일</li>
<li>패키지 이름, 저작자, 라이선스, 의존성 패키지 등을 XML 형식으로 기술</li>
<li>rclcpp와 rclpy 사이에서 약간의 차이가 발생함</li>
</ul>
<blockquote>
<p>** rclcpp의 경우 **</p>
</blockquote>
<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_first_ros_rclcpp_pkg&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;pyo@robotis.com&quot;&gt;pyo&lt;/maintainer&gt;
  &lt;license&gt;TODO: License declaration&lt;/license&gt;
  &lt;buildtool_depend&gt;ament_cmake&lt;/buildtool_depend&gt;
  &lt;depend&gt;rclcpp&lt;/depend&gt;
  &lt;depend&gt;std_msgs&lt;/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><blockquote>
<p>** rclpy의 경우 **</p>
</blockquote>
<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_first_ros_rclpy_pkg&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;pyo@robotis.com&quot;&gt;pyo&lt;/maintainer&gt;
  &lt;license&gt;TODO: License declaration&lt;/license&gt;
  &lt;depend&gt;rclpy&lt;/depend&gt;
  &lt;depend&gt;std_msgs&lt;/depend&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><p>package.xml 파일에 담겨있는 핵심 내용들은 다음과 같다.</p>
<blockquote>
<p>💡 ** 패키지 설정 파일 내용들 **</p>
</blockquote>
<pre><code>&lt;xml&gt; : 문서 문법을 정의하는 문구
&lt;package&gt; ~ &lt;/package&gt; : ROS 패키지 설정 부분
&lt;name&gt; : 패키지 이름
&lt;version&gt; : 패키지 버젼
&lt;descrption&gt; : 패키지에 대한 간단한 설명
&lt;maintainer&gt; : 패키지 관리자 이름 및 이메일
&lt;license&gt; : 라이선스 기재
&lt;url&gt; : 패키지 설명 페이지 주소 등
&lt;author&gt; : 패키지 개발자 이름 및 이메일 주소
&lt;buildtool_depend&gt; : 빌드 툴의 의존성 기술
&lt;build_depend&gt; : 패키지 빌드할 때 필요한 의존성 패키지
&lt;exec_depend&gt; : 패키지 실행시 필요한 의존성 패키지
&lt;test_depend&gt; : 패키지 테스트 시 필요한 의존성 패키지
&lt;export&gt; : 위에 명시되지 않는 내용들을 작성할 때 사용</code></pre><h3 id="빌드-설정-파일cmakeliststxt">빌드 설정 파일(CMakeLists.txt)</h3>
<p>C++의 경우 빌드 시스템으로 CMake를 이용하고 있고(정확히는 ament_cmake), 이에 따라서 빌드 설정 파일을 작성해야 한다. 이 빌드 설정 파일에는 실행 파일 생성, 의존성 패키지 우선 빌드, 링크 생성 등을 설정하게 된다.</p>
<p>CMakeLists.txt 파일 내용은 다음과 같다.</p>
<pre><code>cmake_minimum_required(VERSION 3.5)
# 현재 cmake의 최소 요구 버젼
project(my_first_ros_rclcpp_pkg)
# 프로젝트의 이름

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# 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 dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# ament 빌드를 할 때 요구되는 구성 요소 패키지들

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()</code></pre><p>위 파일에는 빠져있지만, 내가 작성한 인터페이스 파일을 사용할 경우 ** rosidl_generate_interfaces ** 를 사용해서 인터페이스를 추가해야 한다.</p>
<pre><code>set(msg_files
  &quot;msg/Count.msg&quot;
)

set(srv_files
  &quot;srv/Calculation.srv&quot;
)

set(action_files
  &quot;action/Led.action&quot;
)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  ${srv_files}
  ${action_files}
  DEPENDENCIES std_msgs action_msgs
  ADD_LINTER_TESTS
)</code></pre><p>위와 같이 작성하면 인터페이스 헤더 파일을 자동으로 생성한다.</p>
<p>그리고 ** include_directories ** 에서 헤더 파일 폴더를 지정하고, ** add_executable ** 에서 빌드할 때 참조하는 ㅋ코드와 실행 파일 이름을 지정한다. 이후 ** ament_target_dependencies ** 에서 해당 라이브러리 및 실행 파일을 빌드하기에, 해당 라이브러리 및 실행 파일을 빌드하기 전에 생성해야 할 의존성이 있는 인터페이스가 있다면 우선적으로 이를 수행하라는 설정이다.</p>
<p>예시를 들면 다음과 같다.</p>
<pre><code>include_directories(
  include
)

set(PUBLISHER_NODE_NAME publisher)
set(SUBSCRIBER_NODE_NAME subscriber)
set(dependencies
  &quot;examples_msgs&quot;
  &quot;rclcpp&quot;
)

add_executable(${PUBLISHER_NODE_NAME} src/publisher/main.cpp src/publisher/counter.cpp)
ament_target_dependencies(${PUBLISHER_NODE_NAME} ${dependencies})

add_executable(${SUBSCRIBER_NODE_NAME} src/subscriber/main.cpp src/subscriber/observer.cpp)
ament_target_dependencies(${SUBSCRIBER_NODE_NAME} ${dependencies})</code></pre><p>설치 옵션도 빌드 옵션과 마찬가지로, 예시는 다음과 같다.</p>
<pre><code>install(TARGETS
  ${PUBLISHER_NODE_NAME}
  ${SUBSCRIBER_NODE_NAME}
  DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY launch meshes param resource urdf worlds
  DESTINATION share/${PROJECT_NAME}
)</code></pre><p>특정 폴더에 두고 설치 시 함께 포함시켜야 하는 것이 있다면 설치 옵션에서 기술하면 된다.</p>
<h3 id="파이썬-패키지-설정-파일setuppy">파이썬 패키지 설정 파일(setup.py)</h3>
<p>순수한 ROS2 파이썬 패키지에서만 사용하는 설정 파일로, setup.py를 파일명으로 사용한다.</p>
<pre><code>from setuptools import setup

package_name = &#39;my_first_ros_rclpy_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;pyo&#39;,
    maintainer_email=&#39;pyo@robotis.com&#39;,
    description=&#39;TODO: Package description&#39;,
    license=&#39;TODO: License declaration&#39;,
    tests_require=[&#39;pytest&#39;],
    entry_points={
        &#39;console_scripts&#39;: [
        ],
    },
)</code></pre><p>솔직히 다른 것은 읽어보면 너무 간단하고, 중요한 것은 entry_points에 내가 실행하고자 하는 노드의 이름과 경로를 작성하는 것이다.</p>
<h3 id="파이썬-패키지-환경설정-파일setupcfg">파이썬 패키지 환경설정 파일(setup.cfg)</h3>
<p>파이썬 패키지 배포를 위한 환경설정 파일이다. 파일명으로는 setup.cfg를 사용하게 되며 develop 옵션과 install 옵션을 설정해 스크립트의 저장 위치를 설정한다.</p>
<pre><code>[develop]
script-dir=$base/lib/my_first_ros_rclpy_pkg
[install]
install-scripts=$base/lib/my_first_ros_rclpy_pkg</code></pre><h3 id="rqt-플러그인-설정-파일pluginxml">RQt 플러그인 설정 파일(plugin.xml)</h3>
<p>RQt 플러그인으로 패키지를 작성할 때의 필수 구성 요소로 XML 태그를 통해 각 속성을 기술한다. 기본적인 구조는 다음과 같다.</p>
<pre><code>&lt;library path=&quot;src&quot;&gt;
  &lt;class name=&quot;Examples&quot; type=&quot;examples_rqt.examples.Examples&quot; base_class_type=&quot;rqt_gui_py::Plugin&quot;&gt;
    &lt;description&gt;
      A plugin visualizing messages and services values
    &lt;/description&gt;
    &lt;qtgui&gt;
      &lt;group&gt;
        &lt;label&gt;Visualization&lt;/label&gt;
        &lt;icon type=&quot;theme&quot;&gt;folder&lt;/icon&gt;
        &lt;statustip&gt;Plugins related to visualization&lt;/statustip&gt;
      &lt;/group&gt;
      &lt;label&gt;Viewer&lt;/label&gt;
      &lt;icon type=&quot;theme&quot;&gt;utilities-system-monitor&lt;/icon&gt;
      &lt;statustip&gt;A plugin visualizing messages and services values&lt;/statustip&gt;
    &lt;/qtgui&gt;
  &lt;/class&gt;
&lt;/library&gt;</code></pre><p>그 외 패키지 변경로그 파일, 라이선스 파일, README.md 파일 등이 있으며, 이 중에서 가장 핵심은 패키지에 대한 각종 자세한 설명을 기술한 README.md 파일이라고 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] 파일 시스템과 빌드 시스템]]></title>
            <link>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C%EA%B3%BC-%EB%B9%8C%EB%93%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C%EA%B3%BC-%EB%B9%8C%EB%93%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Wed, 18 Sep 2024 13:47:32 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 파일 시스템과 빌드 시스템에 관한 이야기를 해보겠다.
참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24322">https://cafe.naver.com/openrt/24322</a>
<a href="https://cafe.naver.com/openrt/24411">https://cafe.naver.com/openrt/24411</a></p>
<hr>
<h1 id="파일-시스템file-system">파일 시스템(File System)</h1>
<blockquote>
<p>💡 ** 파일 시스템? **
파일 시스템은 ROS2 패키지 및 소스코드 검색, 메시지 파일 / 실행 파일 / 파라미터 설정 및 환경설정 파일에 대한 탐색, 수정, 읽기, 관리 등을 담당하기 위한 시스템</p>
</blockquote>
<h3 id="패키지와-메타패키지">패키지와 메타패키지</h3>
<ul>
<li>ROS2에서는 기본적인 소프트웨어 구성 단위로 패키지(Package)를 사용함</li>
<li>패키지는 공통된 목적을 지닌 패키지들을 모아놓은 메타패키지(meta-package) 단위로 관리될 때도 있음</li>
<li>각 패키지들은 패키지의 정보를 담은 ** package.xml ** 파일과, 패키지 빌드와 관련된 각종 내용을 담은 ** CMakeLists.txt ** 파일을 가지고 있음</li>
</ul>
<h3 id="바이너리-설치와-소스에서-빌드">바이너리 설치와 소스에서 빌드</h3>
<blockquote>
<p>💡 ** 바이너리 설치 **</p>
</blockquote>
<ul>
<li>이미 컴파일이 된 바이너리 파일을 받아서 설치</li>
<li>패키지의 이름을 통해서 설치 가능<pre><code>$ sudo apt install ros-foxy-teleop-twist-joy</code></pre></li>
</ul>
<blockquote>
<p>💡 ** 소스에서 빌드 **</p>
</blockquote>
<ul>
<li>컴파일이 아직 되지 않은 순수 소스코드를 받아서 컴파일을 통해 설치 및 실행</li>
<li>보통 git clone을 통해 리포지터리를 받은 후, 빌드 툴을 활용해서 빌드를 진행<pre><code>$ cd robot_ws/src
$ git clone ~~
$ cd ..
$ colcon build --symlink-install --packages-select ~</code></pre></li>
</ul>
<h3 id="기본-설치-폴더와-사용자-작업-폴더">기본 설치 폴더와 사용자 작업 폴더</h3>
<ul>
<li>ROS2 파일 시스템은 설치 폴더와 사용자 작업 폴더로 구분됨<h4 id="기본-설치-폴더">기본 설치 폴더</h4>
</li>
<li>설치 폴더는 ** /opt ** 폴더 내에 ros 폴더 내 버젼 따라 구분됨</li>
<li>경로는 예를 들면 다음과 같음<pre><code>/opt/ros/foxy</code></pre><blockquote>
<p>** 기본 설치 폴더 내에 존재하는 하위 내용들 **</p>
</blockquote>
</li>
<li>/bin : 실행 가능한 바이너리 파일</li>
<li>/cmake : 빌드 설정 파일</li>
<li>/include : 헤더 파일</li>
<li>/lib : 라이브러리 파일</li>
<li>/opt : 기타 의존 패키지</li>
<li>/share : 패키지의 빌드, 환경설정 파일</li>
<li>local_setup.* : 환경설정 파일</li>
<li>setup.* : 환경설정 파일</li>
</ul>
<h4 id="사용자-작업-폴더">사용자 작업 폴더</h4>
<ul>
<li>사용자가  원하는 곳에 생성해 각종 사용자가 작성하거나 필요한 패키지들을 모아놓고 사용하는 폴더</li>
<li>경로를 예로 들면 다음과 같음<pre><code>/home/user_name/robot_ws</code></pre><blockquote>
<p>** 사용자 작업 폴더 내에 들어가 있는 하위 내용들 **</p>
</blockquote>
</li>
<li>/build : 빌드 설정 파일용 폴더</li>
<li>/install : msg/srv/action 헤더 파일과 사용자 패키지 라이브러리, 실행 파일들의 폴더</li>
<li>/log : 빌드 로깅 파일용 폴더</li>
<li>/src : 사용자 패키지용 폴더</li>
</ul>
<p>사용자 패키지 내에는 다음 내용들이 포함될 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li>/src : C/C++ 코드 파일</li>
<li>/include : C/C++ 헤더 파일용 폴더</li>
<li>/param : 파라미터 파일 폴더</li>
<li>/launch : ros2 launch에 사용할 launch 파일 폴더</li>
<li>/pkg폴더 : 파이썬 코드용 폴더</li>
<li>/test : 테스트 코드 및 테스트 데이터용 폴더</li>
<li>/msg : 메시지 파일용 폴더</li>
<li>/srv : 서비스 파일용 폴더</li>
<li>/action : 액션 파일용 폴더</li>
<li>/doc : 문서용 폴더</li>
<li>package.xml : 패키지 설정 파일</li>
<li>CMakeLists.txt : C/C++ 빌드 설정 파일</li>
<li>setup.py : 파이썬 코드 환경 설정 파일</li>
<li>README.md : 패키지 설명 파일</li>
<li>etc ..</li>
</ul>
<hr>
<h1 id="빌드-시스템과-빌드-툴">빌드 시스템과 빌드 툴</h1>
<ul>
<li>빌드 툴(build tool) : 시스템 전체를 대상으로 함</li>
<li>빌드 시스템(build system) : 단일 패키지를 대상으로 함</li>
</ul>
<p>좀 더 상세한 설명에 대해서 알아보자.</p>
<blockquote>
<p>💡 ** 빌드 시스템 **</p>
</blockquote>
<ul>
<li>단일 패키지를 대상으로 수행</li>
<li>단일 패키지는 가장 간단하게는 RCL부터 많게는 수십개 패키지에 대해 의존성을 가지고 있을 수 있음</li>
<li>단일 패키지가 가지고 있는 의존성(dependencies)을 해결하고 빌드하여 실행 가능한 파일을 생서하게 됨</li>
<li>ROS1의 경우 cmake를, ROS2의 경우 ament_cmake(C++), Python setuptools(파이썬)을 사용</li>
</ul>
<blockquote>
<p>💡 ** 빌드 툴 **</p>
</blockquote>
<ul>
<li>시스템 전체를 대상으로 함</li>
<li>수많은 패키지들 사이의 의존성 그래프를 해석하고, 토폴로지 순서에 따라 각 패키지에 대한 특정 빌드 시스템을 호출 -&gt; 개발환경을 설정하고, 실행 환경까지 구성하게 됨</li>
<li>ROS1에서는 catkin_make, catkin_build, ROS2에서는 colcon을 사용</li>
</ul>
<h3 id="빌드-시스템">빌드 시스템</h3>
<ul>
<li>ROS1에서는 기본적으로 CMake 기반의 catkin을 사용함</li>
<li>이를 위해 CMakeLists.txt 파일을 작성</li>
<li>ROS2에서는 ament를 사용함</li>
<li>C++의 경우 ament_cmake를 사용하며, catkin의 발전 버젼으로 CMakeLists.txt를 사용하는 것까지 동일함</li>
<li>파이썬의 경우 ament_python을 통해서 파이썬 Setuptools를 활용해 파이썬을 순수한 파이썬 모듈과 동일한 수준으로 다룰 수 있게 되었음</li>
</ul>
<h3 id="빌드-툴">빌드 툴</h3>
<ul>
<li>ROS1의 경우 catkin_make, catkin_tools 등 다양한 빌드 툳릉리 지원되었음</li>
<li>ROS2의 경우 현재 colcon(Collective Construction)을 사용하고 있음</li>
<li>colcon은 ROS1과 ROS2 모두를 지원하기 위해 만들어진 통합 빌드 툴</li>
</ul>
<h3 id="ros2의-패키지-생성">ROS2의 패키지 생성</h3>
<ul>
<li>패키지 폴더를 만들고, 각종 필수 파일 및 폴더들 직접 생성</li>
<li>CLI 명령어를 통해서 사용</li>
</ul>
<p>이 중 더 간단하고 쉬운 것은 CLI 명령어를 통해서 하는 것이다.
패키지를 생성하는 방법은 다음과 같다.</p>
<pre><code>$ ros2 pkg creat &lt;pkg_name&gt; --build-type &lt;build_type&gt; --dependencies &lt;의존성_패키지1&gt; &lt;의존성_패키지2&gt;</code></pre><ul>
<li>C++의 경우 ament_cmake를 빌드 타입으로 설정</li>
<li>파이썬의 경우 ament_python을 빌드 타입으로 설정</li>
<li>GUI 프로그래묭 파이썬의 경우 ament_cmake를 설정해야함</li>
</ul>
<p>위 명령어를 입력하면 package.xml 파일과 CMakeLists.txt 파일을 자동으로 생성하게 된다.</p>
<p>예를 들어 패키지를 하나 생성해보면 다음과 같다.</p>
<pre><code>$ cd robot_ws/src
$ ros2 pkg create my_first_ros_rclcpp_pkg --build-type ament_cmake --dependencies rclcpp std_msgs
// 혹은
$ ros2 pkg create my_first_ros_rclpy_pkg --build-type ament_python --dependencies rclpy std_msgs</code></pre><p>의존성 패키지 설정은 package.xml 파일에 가서도 할 수 있다.</p>
<h3 id="패키지-빌드">패키지 빌드</h3>
<p>ROS2에서 특정 패키지 혹은 전체 패키지를 빌드하기 위해서는 colcon 빌드 툴을 사용한다. 예시를 통해 알아보면 다음과 같다.</p>
<pre><code>$ cd robot_ws
$ colcon build --symlink-install // 전체 패키지들 빌드
$ colcon build --symlink-install --packages-select &lt;pkg_name&gt;
// 특정 패키지만 빌드
$ colcon build --symlink-install --packages-up-to &lt;pkg_name&gt;
// 특정 패키지의 의존성 패키지들까지 한꺼번에 빌드</code></pre><h3 id="빌드-시스템-부가-기능">빌드 시스템 부가 기능</h3>
<h4 id="vcstool">vcstool</h4>
<blockquote>
<p>💡 ** vcstool **
vcs는 Version Control System의 약어로 각 리포지터리들을 소스로 설치하고 빌드할 때 이를 간편하게 만들어주도록 하는 툴이다.</p>
</blockquote>
<h4 id="rosdep">rosdep</h4>
<blockquote>
<p>💡 ** rosdep **
빌드 툴들은 각 패키지의 의존성을 고려해 빌드는 해주나, 의존성 자체를 해결해주지는 않는다. ROS에서는 이를 해결하기 위해서 rosdep이라는 의존성 해결 툴을 사용하고 있는데, package.xml 파일에 기술된 의존성 정보를 가지고 의존성 패키지들을 자동으로 설치해주는 역할을 한다.
rosdep을 사용하기 위해서는 먼저 데이터베이스를 최신화 해줘야 한다.</p>
</blockquote>
<pre><code>$ sudo rosdep init
$ rosdep update</code></pre><h4 id="bloom">bloom</h4>
<blockquote>
<p>💡 ** bloom **
바이너리 패키지 관리 툴로, 바이너리 패키지를 관리하기 위한 메타 데이터를 생성하고, dpkg와 같은 플랫폼 종속 도구가 바이너리 패키지를 빌드하는데 사용한다.</p>
</blockquote>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] ROS2의 단위, 좌표, 시간]]></title>
            <link>https://velog.io/@hy_k/ROS2-ROS2%EC%9D%98-%EB%8B%A8%EC%9C%84-%EC%A2%8C%ED%91%9C-%EC%8B%9C%EA%B0%84</link>
            <guid>https://velog.io/@hy_k/ROS2-ROS2%EC%9D%98-%EB%8B%A8%EC%9C%84-%EC%A2%8C%ED%91%9C-%EC%8B%9C%EA%B0%84</guid>
            <pubDate>Wed, 18 Sep 2024 03:57:28 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 표준 단위(standard unit), 좌표 표현, 시간에 대해서 공부를 해보겠다.</p>
<p>참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24272">https://cafe.naver.com/openrt/24272</a>
<a href="https://cafe.naver.com/openrt/24274">https://cafe.naver.com/openrt/24274</a>
<a href="https://cafe.naver.com/openrt/24953">https://cafe.naver.com/openrt/24953</a></p>
<hr>
<h1 id="ros2의-표준-단위">ROS2의 표준 단위</h1>
<blockquote>
<p>💡 ** ROS2 표준 단위 **
ROS 커뮤니티에서는 ROS 프로그래밍에 사용하는 표준 단위로, SI 유도 단위(SI Derived Unit)을 표준 단위로 결정하였다. SI 단위는 총 7개이고, 유도단위는 20개가 존한다. 이와 관련해 정리하면 다음과 같다.</p>
</blockquote>
<pre><code>- 길이(lenght) : 미터(m)
- 질량(mass) : 킬로그램(kg)
- 시간(time) : 초(second, s)
- 전류(current) : 암페어(ampere, A)
- 평면각(angle) : 라디안(radian, rad)
- 주파수(frequency) : 헤르츠(Hz)
- 힘(force) : 뉴턴(N)
- 일률(power) : 와트(W)
- 전압(voltage) : 볼트(V)
- 온도(temperature) : 섭씨(celcius)
- 자기장(magnetism) : 테슬라(T)</code></pre><hr>
<h1 id="ros2의-좌표-표현">ROS2의 좌표 표현</h1>
<p>단위와 마찬가지로, ROS2에서는 좌표 표현 역시 통일할 필요가 있다. 아니면, 최소한 서로 다른 좌표계를 제대로 정의하고 이에 대한 변환 표현을 도입할 필요가 있다.</p>
<p>예를 들어서, 컴퓨터 비전의 경우 카메라 좌표계를 사용한다. 카메라 좌표계는 z forward, x right, y down을 기본 좌표계로 사용하고 있다. 반면, 로봇은 기본적으로 x forward, y left, z up을 기본으로 사용하고 있다. 따라서 각 좌표계를 제대로 숙지하고, 이들에 대한 변환 행렬을 올바르게 구하는 것은 매우 중요하다.</p>
<p>ROS2는 이러한 문제로 인해서 좌표를 통일하였다. ROS2의 가장 기본적인 3축 좌표는 다음과 같다. 일반적으로 오른손 법칙을 통해서 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/hy_k/post/e4888ea5-5ba2-41a0-89e7-f74734f9a161/image.png" alt="">
출처: <a href="https://cafe.naver.com/openrt/24274">https://cafe.naver.com/openrt/24274</a></p>
<p>Gazebo 등 ROS 시뮬레이터에서는 이러한 방향 표현을 효과적으로 표현하기 위해서 3원색을 사용한다.</p>
<ul>
<li>x축 : 빨간색</li>
<li>y축 : 초록색</li>
<li>z축 : 파란색</li>
</ul>
<p>그 외 좌표계들은 다음과 같다.</p>
<h3 id="enu-좌표계">ENU 좌표계</h3>
<p>ENU는 East North Up의 줄임말로, 드론 / 실외 자율주행 로봇 등에 주로 사용되는 좌표계이다.</p>
<h3 id="optical-좌표계">Optical 좌표계</h3>
<p>컴퓨터 비전에서 사용하는 카메라 좌표계로 앞서 얘기하였듯 z forward, x right, y down 좌표를 사용한다. 이 좌표계를 사용하는 경우 보통 _optical 접미사를 통해서 구분지어준다.</p>
<h3 id="ned-좌표계">Ned 좌표계</h3>
<p>실외 시스템의 경우 ENU 대신 NED(North East Down) 좌표계를 사용하기도 한다. 이때는 _ned 접미사를 통해서 구현한다.</p>
<h3 id="회전-표현">회전 표현</h3>
<blockquote>
<p>💡 ** ROS2의 회전 표현 **</p>
</blockquote>
<ol>
<li>쿼터니언 : 간결한 표현 방식(x,y,z,w)</li>
<li>회전 변환 행렬 </li>
<li>고정축 roll, pitch, yaw : 각속도에 사용</li>
<li>오일러 각도 : ROS 커뮤니티에서는 별로 안좋아하는 분위기다.</li>
</ol>
<hr>
<h1 id="ros2의-시간-표현">ROS2의 시간 표현</h1>
<blockquote>
<p>** 왜 시간이 중요한가? **
센서들로부터 얻는 데이터들이 시간에 따른 변화량이 중요할 때가 많기 때문이다. 따라서 센서들 간의 시간 동기화가 매우 중요하다. 특히, 해당 센서 데이터들이 퍼블리시 된 정확한 시간이 중요하다고 할 수 있다.</p>
</blockquote>
<p>ROS2에서는 퍼블리시 되는 토픽에 주요 데이터 뿐만 아니라 해당 토픽이 퍼블리시 되는 시간을 함게 포함시킬 수 있다. 대표적으로 다음의 메시지가 있다.</p>
<pre><code>std_msgs/msg/header -&gt; stamp 및 frame id 포함</code></pre><p>또한, ROS2에서는 기준이 되는 시계를 노드가 생성될 때 정해지도록 하였다. 예를 들어 다음 명령어를 입력해보자.</p>
<pre><code>$ ros2 run time_rclcpp_example time_example --ros-args -p use_sim_time:=False</code></pre><p>이 명령어를 입력하면, time_example 노드가 가진 시계의 시간을 확인할 수 있다. System clock을 ROS2에서는 기본 시계로 사용하며, 국제 표준시인 ** 협정 세계시(UTC) ** 를 사용하고 있다. rclcpp에서는 std::chrono 라이브러리를, rclpy에서는 time 모듈을 캡슐화 해서 사용한다.</p>
<h3 id="시간-추상화">시간 추상화</h3>
<p>ROS2에서는 기본적으로 UTC에 기반한 시간을 제공하지만, 필요에 따라서 시점을 다르게 바꿀 수 있는 시계도 제공한다. 이를 시간을 추상화(Abstraction) 해서 제공한다고 말한다. ROS2는 추상화 한 시간을 총 3가지 제공한다.</p>
<p>이는 RCL에 포함된 time.h 헤더파일을 살펴보면 확인 가능하다.</p>
<pre><code>&lt;!--rcl/time.h--&gt;
enum rcl_time_source_type_t
{
    RCL_TIME_SOURCE_UNINITIALIZED = 0,
    RCL_ROS_TIME,
    RCL_SYSTEM_TIME,
    RCL_STEADY_TIME
};</code></pre><blockquote>
<p>💡 ** System Time **
위에서 다룬 system clock을 사용하는 시간을 의미한다. 기본적으로 단조증가(monotonic) 하지만, 타임 서버 동기화를 통해 거꾸로 가는 경우도 있다.</p>
</blockquote>
<pre><code>$ sudo ntpdate ntp.ubuntu.com</code></pre><p>이러한 명령어처럼 특정 서버와의 시간 동기화를 통해 기존 서버와는 다른 시간으로 맞출 수도 있다.</p>
<blockquote>
<p>💡 ** ROS Time **
시뮬레이션 용도로 많이 사용한다. use_sim_time 파라미터를 통해서 사용하며, 이를 True로 설정하면 /clock 토픽을 sub하기 전까지 시간을 계속 0으로 초기화 한다.</p>
</blockquote>
<blockquote>
<p>💡 ** Steady Time **
Hardware Timeouts를 사용한 시간으로, 무조건 단조증가한다.</p>
</blockquote>
<h3 id="time-api">Time API</h3>
<p>ROS2에서는 시간과 관련된 API를 제공한다.</p>
<ul>
<li>time API</li>
<li>duration API</li>
<li>rate API</li>
</ul>
<p>예제 코드를 보면 도움이 될 것이다.
먼저 rclcpp 예제 코드를 살펴보자.</p>
<pre><code>#include &lt;memory&gt;
#include &lt;utility&gt;

#include &quot;rclcpp/rclcpp.hpp&quot;
#include &quot;rclcpp/time_source.hpp&quot;
#include &quot;std_msgs/msg/header.hpp&quot;

int main(int argc, char *argv[]){
    rclcpp::init(argc, argv); # 노드 초기화

    auto node = rclcpp::Node::make_shared(&quot;time_example_node&quot;);
    # 노드 선언
    auto time_publisher = node-&gt;create_publisher&lt;std_msgs::msg::Header&gt;(&quot;time&quot;,10);
    std_msgs::msg::Header  msg;
    # 퍼블리셔 및 필요한 메시지(인터페이스) 선언

    rclcpp::WallRate loop_rate(1.0);
    # 토픽 발행 주기 1Hz로 설정
    rclcpp::Duration duration(1,0);

    while(rclcpp::ok()){
        static rclcpp::Time past = node-&gt;now();

        rclcpp::Time now = node-&gt;now();
        RCLCPP_INFO(node-&gt;get_logger(), &quot;sec %lf nsec %ld&quot;, now.seconds(), now.nanoseconds());

        if((now-past).nanoseconds()*1e-9 &gt; 5){
            RCLCPP_INFO(node-&gt;get_logger(), &quot;Over 5 seconds!&quot;);
            past = node-&gt;now();
        }
        msg.stamp = now + duration;
        time_publisher-&gt;publish(msg);

        rclcpp::spin_some(node);
        loop_rate.sleep();
    }
    rclcpp::shutdown();
}</code></pre><p>다음으로는 rclpy에서서 time을 다루는 예제 코드를 살펴보자.</p>
<pre><code>import rclpy
from rclpy.duration import Duration
from std_msgs.msg import Header

def main(args=None):
    rclpy.init(args=args)

    node = rclpy.create_node(&quot;time_example_node&quot;)
    time_publisher = node.create_publisher(Header, &quot;time&quot;, 10)
    msg = Header()

    rate = node.create_rate(1.0)
    duration = Duration(seconds=1, nanoseconds=0)
    past = node.get_clock().now()

    try:
        while rclpy.ok():
            now = node.get_clock().now()
            seconds, nanoseconds = now.seconds_nanoseconds()
            node.get_logger().info(&quot;sec {0} nsec {1}&quot;.format(seconds, nanoseconds)

            if((now-past).nanoseconds * 1e-9) &gt; 5 :
                node.get_logger().info(&quot;Over 5 seconds&quot;)
                past = node.get_clock().now()

            msg.stamp = (now+duration).to_msg()
            time_publisher.publish(msg)

            rclpy.spin_once(node)
            rate.sleep()

    except KeboardInterrupt:
        node.get_logger().info(&quot;Keyboard Interrupt SIGINT&quot;)
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == &quot;__main__&quot;:
    main()</code></pre><p>코드를 보면 알겠지만, Time 클래스는 시간을 다룰 수 있는 오퍼레이터를 제공하고, 그 결과를 second 혹은 nanosecond 단위로 반환해준다.</p>
<ul>
<li>second : double 형으로 반환</li>
<li>nanosecond : unsigned int64 형으로 반환. 더 정확함</li>
</ul>
<p>그리고 ROS2에서는 now() 함수를 통해서 노드 시간을 확인 가능하다.</p>
<pre><code>rclcpp::Time now = node-&gt;now();</code></pre><p>또한 ROS2에서는 Duration 클래스를 통해 기간(3시간 전 등등) 단위의 시간을 다룰 수 있게 하며, 그 결과를 seconds 혹은 nanoseconds 단위로 반환한다. Duration은 Time과 연산이 가능해서 직관적으로 계산이 가능하다. 다음 예시 코드는 실제 시간보다 1초 느린 값을 계산하는 코드이다.</p>
<pre><code>rclcpp::Duration duration(1,0);
msg.stamp = now + duration;
time_publisher-&gt;publish(msg);</code></pre><p>그리고 ROS2에서는 Rate 클래스도 제공하여, 반복문에서 특정 주기를 유지시켜주는 API를 제공한다. 앞선 예시 코드에서 Hz 단위로 주기를 설정한 후 sleep 함수를 통해 주기에 맞춰 실행되도록 설정한 것을 볼 수 있다. 여기서 중요한 것은  ** Rate는 System Clock을, WallRate는 Steady Clock을 사용한다는 것이다. ** 하지만 ROS2에서는 Timer API를 별개로 제공하고 있기 때문에, 이를 사용하는 것이 나을 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] CLI 명령어와 RQt]]></title>
            <link>https://velog.io/@hy_k/ROS2-CLI-%EB%AA%85%EB%A0%B9%EC%96%B4%EC%99%80-RQt</link>
            <guid>https://velog.io/@hy_k/ROS2-CLI-%EB%AA%85%EB%A0%B9%EC%96%B4%EC%99%80-RQt</guid>
            <pubDate>Thu, 12 Sep 2024 06:17:56 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2 CLI(Command Line Interface) 명령어, 그리고 ROS2 개발에 유용하게 사용할 수 있는 개발 도구인 RQt에 대해서 알아보자.</p>
<p>참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24191">https://cafe.naver.com/openrt/24191</a>
<a href="https://cafe.naver.com/openrt/24231">https://cafe.naver.com/openrt/24231</a></p>
<hr>
<h1 id="cli-명령어">CLI 명령어</h1>
<p>ROS2에서 CLI를 사용하려면 다음과 같다.</p>
<pre><code>$ ros2 &lt;verbs&gt; &lt;sub-verbs&gt; &lt;options&gt; &lt;args&gt;</code></pre><p>여담으로 이러한 CLI는 tab을 통해서 자동 완성이 가능하다.
즉, 쉽게 사용할 수 있다는 뜻이다.</p>
<p>그리고 제대로 된 명령어가 기억나지 않는다면 -h 옵션을 통해서 도움말이나 사용법, CLI 정보를 확인할 수 있다.</p>
<pre><code>$ ros2 -h
$ ros2 node -h
$ ros2 node info -h</code></pre><p>CLI 명령어들을 하나씩 살펴보면 다음과 같이. 솔직히 말해서 다 외우는 것은 말이 안되고, 필요할 때 찾아서 사용하고 자주 사용하는 것만 익숙해져도 충분하다고 생각한다.</p>
<h3 id="노드-실행-명령어">노드 실행 명령어</h3>
<h4 id="단일-노드">단일 노드</h4>
<pre><code>$ ros2 run &lt;package&gt; &lt;node_name&gt;
// 단, node에 따라 복 노드를 내부적으로 실행 가능</code></pre><h4 id="복수-노드">복수 노드</h4>
<pre><code>$ ros2 launch &lt;package&gt; &lt;launch_file&gt;</code></pre><h3 id="각종-정보-명령어">각종 정보 명령어</h3>
<h4 id="패키지">패키지</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 pkg create : 새로운 패키지 생성</li>
<li>ros2 pkg executables : 지정 패키지의 실행 파일 목록 출력</li>
<li>ros2 pkg list : 사용 가능한 패키지 목록 출력</li>
<li>ros2 pkg prefix : 지정 패키지의 저장 위치 출력</li>
<li>ros2 pkg xml : 지정 패키지의 패키지 정보 파일 출력</li>
</ol>
<h4 id="노드">노드</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 node info : 실행 중인 노드 중 지정한 노드 정보 출력</li>
<li>ros2 node list : 실행 중인 모든 노드의 정보 출력</li>
</ol>
<h4 id="토픽">토픽</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 topic bw : 지정 토픽의 대역폭 출력</li>
<li>ros2 topic delay : 지정 토픽의 지연시간 측정</li>
<li>ros2 topic echo : 지정 토픽의 데이터 출력</li>
<li>ros2 topic find : 지정 타입을 사용하는 토픽 이름 출력</li>
<li>ros2 topic hz : 지정 토픽의 주기 측정</li>
<li>ros2 topic info : 지정 토픽의 정보 출력</li>
<li>ros2 topic list : 사용 가능한 토픽 목록 출력</li>
<li>ros2 topic pub : 지정 토픽의 발행</li>
<li>ros2 topic type : 지정 토픽의 타입 출력</li>
</ol>
<h4 id="서비스">서비스</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 service call : 지정 서비스의 서비스 요청 전달</li>
<li>ros2 service find : 지정 서비스 타입의 서비스 출력</li>
<li>ros2 service list : 사용 가능한 서비스 목록 출력</li>
<li>ros2 service type : 지정 서비스의 타입 출력</li>
</ol>
<h4 id="액션">액션</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 actcion info : 지정 액션의 정보 출력</li>
<li>ros2 action list : 사용 가능한 액션 목록 출력</li>
<li>ros2 action send_goal : 지정 액션의 액션 목표 전송</li>
</ol>
<h4 id="인터페이스">인터페이스</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 interface list : 사용 가능한 모든 인터페이스 목록 출력</li>
<li>ros2 interface package : 특정 패키지에서 사용 가능한 인터페이스 목록 출력</li>
<li>ros2 interface packages : 인터페이스 패키지들의 목록 출력</li>
<li>ros2 interface proto : 지정 패키지의 프로토타입 출력</li>
<li>ros2 interface show : 지정 인터페이스의 데이터 형태 출력</li>
</ol>
<h4 id="파라미터">파라미터</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 param delete : 지정 파라미터 삭제</li>
<li>ros2 param describe : 지정 파라미터 정보 출력</li>
<li>ros2 param dump : 지정 파라미터 저장(yaml 파일)</li>
<li>ros2 param get : 지정 파라미터 읽기</li>
<li>ros2 param list : 사용 가능한 파라미터 목록 출력</li>
<li>ros2 param set : 지정 파라미터 쓰기</li>
</ol>
<h4 id="rosbag-파일">rosbag 파일</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 bag info : 저장된 rosbag 정보 출력</li>
<li>ros2 bag play : rosbag 재생</li>
<li>ros2 bag record : bag 파일 저장</li>
</ol>
<h3 id="ros2-기능-보조-명령어">ROS2 기능 보조 명령어</h3>
<h4 id="extension">extension</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 extensions - a</li>
<li>ros2 extensions -v</li>
</ol>
<p>ROS2 CLI의 Extensions 출력</p>
<h4 id="extension_points">extension_points</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 extension_points -a</li>
<li>ros2 extension_points -v</li>
</ol>
<p>ROS2 extension point 목록 출력</p>
<h4 id="daemon">daemon</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 daemon start : daemon 시작</li>
<li>ros2 daemon status : daemon 상태 보기</li>
<li>ros2 daemon stop : daemon 정지</li>
</ol>
<h4 id="multicast">multicast</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 multicast receive : 수신</li>
<li>ros2 multicast send : 송신</li>
</ol>
<h4 id="doctor">doctor</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 doctor hello (-r)</li>
<li>ros2 doctor hello (-rf)</li>
<li>ros2 doctor hello (-iw)</li>
</ol>
<p>ROS 설정 및 네트워크, 패키지 버젼, RMW(ROS MiddleWare)와 같은 잠재적 문제를 진단하기 위해서 도입된 도구이다.</p>
<h4 id="wtf">wtf</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 wtf hello (-r)</li>
<li>ros2 wtf hello (-rf)</li>
<li>ros2 wtf hello (-iw)</li>
</ol>
<p>doctor와 동일한 역할을 수행한다.</p>
<h4 id="lifecycle">lifecycle</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 lifecycle get : 라이프 사이클 정보 출력</li>
<li>ros2 lifecycle list : 지정 노드의 사용 가능한 상태 천이 목록 출력</li>
<li>ros2 lifecycle nodes : 라이프 사이클을 사용하는 노드 목록 출력</li>
<li>ros2 lifecycle set : 라이프 사이클 상태 전환 트리거</li>
</ol>
<h4 id="component">component</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 component list : 실행 중인 컨테이너와 컴포넌트 목록 출력</li>
<li>ros2 component load : 지정 컨테이너 노드의 특정 컴포넌트 실행</li>
<li>ros2 component standalone : 표준 컨테이너 노드로 특정 컴포넌트 실행</li>
<li>ros2 component types : 사용 가능한 컴포넌트 목록 출력</li>
<li>ros2 component unload : 지정 컴포넌트의 실행 중지</li>
</ol>
<h4 id="security">security</h4>
<blockquote>
</blockquote>
<ol>
<li>ros2 security create_key : 보안키 생성</li>
<li>ros2 security create_keysore : 보안키 저장소 생성</li>
<li>ros2 security create_permission : 보안 허가 파일 생성</li>
<li>ros2 security generate_artifacts : 보안 정책 파일을 이용해 보안키 및 보안 허가 파일 생성</li>
<li>ros2 security generate_policy : 보안 정책 파일(policy.xml) 생성</li>
<li>ros2 security list_keys : 보안키 목록 출력</li>
</ol>
<hr>
<h1 id="rqt">RQt</h1>
<blockquote>
<p>💡 ** RQt란? **
RQt는 플러그인(plugin) 형태로 다양한 도구와 인터페이스를 구현할 수 있는 ROS의 GUI 프레임워크인 동시에, 다양한 목적의 GUI 툴들을 집합시켜놓은 ROS 종합 GUI 툴박스(toolbox)라고 할 수 있다.</p>
</blockquote>
<p>프레임워크기 때문에 다양한 기능들을 API 형태로 제공하며, 동시에 내가 개발한 GUI 툴들을 플러그인 형태로 쉽게 추가할 수 있다는 장점을 가지고 있다.</p>
<p><img src="https://velog.velcdn.com/images/hy_k/post/cd4d4edd-5c44-413c-ad6f-1bafbad187a6/image.png" alt=""></p>
<pre><code>$ rqt</code></pre><p>위 명령어를 통해서 실행하면 다음과 같이 텅 빈 화면이 나오는데, 여기서 왼쪽 상단의 플러그인 메뉴에 접속하여 원하는 기능을 쉽게 실행할 수 있다. 예를 들어, 플러그인 메뉴에서,</p>
<pre><code>Plugins -&gt; Topics -&gt; Topic Monitor</code></pre><p>를 차례대로 실행하면 다음과 같은 화면을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hy_k/post/c601594e-7ebb-44eb-940b-da6c83b82e9c/image.png" alt=""></p>
<p>혹은 CLI를 통해서도 실행이 가능하다.</p>
<pre><code>$ ros2 run rqt_msg rqt_msg</code></pre><p>이런 식으로 자주 쓰는 플러그인은 외워두고 CLI 명령어를 통해서 사용이 가능하다.
또 다른 방법으로는 단축 명령어를 사용하면 된다.
가장 대표적으로 쓰이는, 가장 많이 쓰이는, 노드 및 메시지 통신 사이의 관계를 도식화 하는 명령어는 다음과 같다.</p>
<pre><code>$ rqt_graph</code></pre><p>RQt 플러그인들의 종류에 대해서는, 다 적는 것은 현실적으로 어려우므로 다음 링크를 참조하면 된다. 또한 각종 플러그인들에 대한 사용 예시 역시 해당 링크를 참조하면 된다.
<a href="https://cafe.naver.com/openrt/24231">https://cafe.naver.com/openrt/24231</a></p>
<p>예를 들어, RQt 플러그인을 통해서 Node 간의 그래프를 그리면 다음과 같다.
<img src="https://velog.velcdn.com/images/hy_k/post/396a7e01-9154-4485-8d8f-9c259aa6aa40/image.png" alt=""></p>
<p>또, RQt 플러그인을 통해서 그래프(plot)를 그리면 다음과 같다.
<img src="https://velog.velcdn.com/images/hy_k/post/530b7016-ea83-49f6-b2ee-f7cf4bcdca11/image.png" alt=""></p>
<p>거기에 웹캠 등의 영상 데이터가 토픽으로 발행되고 있을 경우(이때 인터페이스는 sensor_msgs/msg/Image를 사용한다), 이것 역시 RQt를 통해서 이미지를 확인할 수 있다. 만약 웹캠 등이 없다면 다음 명령어를 실행해보자.</p>
<pre><code>$ ros2 run image_tools cam2image --ros-args -p burger_mode:=true</code></pre><p>그리고 rqt를 통해서 image_view를 켜면 다음 화면을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hy_k/post/96b1ead5-006f-408e-abc9-79717f288961/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] 파라미터와 인터페이스]]></title>
            <link>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@hy_k/ROS2-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Thu, 12 Sep 2024 04:43:53 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 파라미터(parameter)와, 메시지 통신에 사용되는 인터페이스(interface)에 대해서 다뤄보도록 하자.</p>
<p>참고한 링크는 다음과 같다.
<a href="https://cafe.naver.com/openrt/24165">https://cafe.naver.com/openrt/24165</a>
<a href="https://cafe.naver.com/openrt/24241">https://cafe.naver.com/openrt/24241</a>
그리고 해당 링크를 기반으로 작성된 서적,
&quot;ROS2로 시작하는 로봇 프로그래밍&quot;도 참고하였다.</p>
<hr>
<h1 id="파라미터">파라미터</h1>
<p>ROS1에서는 파라미터 서버(param server)를 ROS Master의 일종으로 관리했음을 알 수 있다. ROS2에서도 큰 틀에서는 유사하지만, 조금 달라진 부분이 있다. 달라진 부분에 대해서 알아보자.
<img src="blob:https://velog.io/7e76db2c-ab85-47b5-ae64-1208dff40829" alt="업로드중..">
출처링크: <a href="https://cafe.naver.com/openrt/24165">https://cafe.naver.com/openrt/24165</a></p>
<blockquote>
<p>💡** ROS2의 파라미터 **
ROS2에서 파라미터는 위 이미지처럼, 각 노드에서 파라미터 서버를 실행시켜, 외부의 파라미터 클라이언트(parameter client)를 통해서 파라미터를 변경할 수 있다. 이러한 점에서 서비스와 매우 유사하다고 볼 수 있다(서비스 역시 서비스 클라이언트의 요청을 받아, 서비스 응답을 수행하는 서비스 서버가 존재하므로).</p>
</blockquote>
<p>다만, 완전히 동일하지는 않다. 차이점은 다음과 같이 정리할 수 있다.</p>
<blockquote>
<p>💡** 파라미터와 서비스의 차이 **</p>
</blockquote>
<ul>
<li>서비스의 경우 요청과 응답으로 구성된다. 이는 특정 요청을 수행하기 위함이다.</li>
<li>반면 파라미터의 경우 노드 내 파라미터를 서비스와 유사하게 통신하여 노드 내부 혹은 외부에서 쉽게 지정 및 변경, 사용이 가능하게 하는 목적이다.</li>
</ul>
<p>파라미터 관련 기능은 RCL(ROS Client Library)의 기본 기능으로, 모든 노드는 자기 자신만의 파라미터 서버를 가지게 된다. 그리고 각 노드는 기본적으로 파라미터 클라이언트 기능 수행이 가능하기 때문에, 각 노드들의 매개변수를 일종의 글로벌 변수처럼 자유롭게 사용할 수 있다는 점도 특징이다.</p>
<p>그리고 yaml 형식의 파일로 파라미터 설정 파일을 만들어 파라미터 값 초기화 및 노드 실행 시 파라미터 설정 파일 호출이 가능하다는 점도 ROS2 기반 프로그래밍에서 매우 유리한 부분이다.</p>
<h3 id="파라미터-관련-명령어">파라미터 관련 명령어</h3>
<h4 id="파라미터-목록-확인">파라미터 목록 확인</h4>
<pre><code>$ ros2 param list</code></pre><p>현재 실행 중인 노드에 속해있는 모든 파라미터 목록을 확인할 수 있다. 다행히도, 각 노드별로 파라미터들이 구분지어서 출력되게 된다.</p>
<h4 id="파라미터-내용-확인">파라미터 내용 확인</h4>
<pre><code>$ ros2 param describe &lt;param_name&gt;</code></pre><p>다음 내용들이 출력된다.</p>
<ul>
<li>파라미터 이름</li>
<li>파라미터 자료형</li>
<li>파라미터에 대한 간단한 설명(description)</li>
<li>파라미터 값</li>
</ul>
<h4 id="파라미터-내용-읽기">파라미터 내용 읽기</h4>
<pre><code>$ ros2 param get &lt;node_name&gt; &lt;param_name&gt;</code></pre><p>노드 이름과 파라미터 이름을 입력하면 파라미터의 현재 값을 읽어들여 확인이 가능하다.</p>
<h4 id="파라미터-내용-쓰기">파라미터 내용 쓰기</h4>
<pre><code>$ ros2 param set &lt;node_name&gt; &lt;param_name&gt; &lt;param_value&gt;</code></pre><p>노드 이름, 파라미터 이름, 그리고 값을 통해 원하는 파라미터에 대한 값을 변경할 수 있다.</p>
<h4 id="파라미터-저장">파라미터 저장</h4>
<pre><code>$ ros2 param dump &lt;node_name&gt;</code></pre><p>set 명령어를 통해서 파라미터 값을 변경한다고 하더라도, 노드를 종료하고 재시작한다면 다시 파라미터 값이 초기화 된다. 따라서 변화시킨 파라미터 값을 또 사용하고 싶다면 파일로 저장했다가 불러오는 식으로 기능해야 한다.</p>
<p>ros2 param dump 명령어는 현재 폴더에 해당 노드 이름으로 파라미터 설정 파일을 yaml 파일로 저장하게 된다.</p>
<p>노드 실행 시 dump 명령어를 통해서 저장한 파라미터 파일을 불러와 실행하고 싶다면, 다음과 같이 옵션을 지정해주면 된다.</p>
<pre><code>$ ros2 run &lt;pkg_name&gt; &lt;node_name&gt; --ros-args --params-file &lt;yaml_name&gt;</code></pre><p>yaml_name은 저장되어 있는 yaml 파일 이름이다.</p>
<h4 id="파라미터-삭제">파라미터 삭제</h4>
<pre><code>$ ros2 param delete &lt;param_name&gt;</code></pre><p>파라미터 이름을 지정해서 원하는 파라미터를 삭제할 수 있다.</p>
<hr>
<h1 id="인터페이스">인터페이스</h1>
<p>기존 포스팅에서 다루었듯 ROS2의 노드 간의 데이터를 주고받을 때에는 메시지 통신으로 토픽, 서비스, 액션을 사용하는데, 이 때 메시지를 주고받기 위한 수단을 인터페이스라고 한다. ROS2의 인터페이스에는 ROS2에 추가된 IDL(Interface Definition Language), ROS1과 동일한 간단한 자료형(data type), 간단한 자료구조(data structure), 배열(array, rclpy의 경우 list나 dictionary도 어쩌면 포함)을 사용할 수 있다.</p>
<p>이에 대한 예시 링크들은 다음과 같다.
<a href="https://github.com/ros2/common_interfaces/tree/foxy/std_msgs">https://github.com/ros2/common_interfaces/tree/foxy/std_msgs</a>
<a href="https://github.com/ros2/common_interfaces/blob/foxy/geometry_msgs/msg/Twist.msg">https://github.com/ros2/common_interfaces/blob/foxy/geometry_msgs/msg/Twist.msg</a>
<a href="https://github.com/ros2/common_interfaces/blob/foxy/sensor_msgs/msg/LaserScan.msg">https://github.com/ros2/common_interfaces/blob/foxy/sensor_msgs/msg/LaserScan.msg</a></p>
<p>간단한 인터페이스는 다음과 같이 작성할 수 있다.</p>
<pre><code>interface_type interface_name</code></pre><p>각 자료형이 RCL에서 제공하는 대표언어(C++, 파이썬)의 어떤 자료형과 매칭되는지는 다음 링크를 참고하면 된다.
<a href="https://cafe.naver.com/openrt/24241">https://cafe.naver.com/openrt/24241</a></p>
<p>각 데이터 통신과 매칭하면 다음과 같다.</p>
<ul>
<li>토픽 = msg</li>
<li>서비스 = srv</li>
<li>액션 = action</li>
</ul>
<p>인터페이스는 정수, 실수(부동소수점), 논리형(bool)과 같은 단순 자료형부터, 간단한 자료구조나 메시지가 나열된 배열 등을 사용하게 된다.</p>
<p>이러한 인터페이스 파일은 다음과 같이 정의가 가능하다.</p>
<pre><code>fieldtype fieldname</code></pre><p>예를 들어서, geometry_msgs에 정의되어 있는 Vector3.msg 인터페이스는 당므과 같이 정의되어 있다.</p>
<pre><code>float64 x
float64 y
float64 z</code></pre><p>ROS2에서 지원하는 각 프로그래밍 언어별 인터페이스는 다음 링크를 참고하거나, 아니면 &quot;ROS2로 시작하는 로봇 프로그래밍&quot; 서적 p.159를 참고하면 된다.
<a href="https://cafe.naver.com/openrt/24241">https://cafe.naver.com/openrt/24241</a></p>
<h3 id="인터페이스-관련-명령어">인터페이스 관련 명령어</h3>
<pre><code>$ ros2 interface show &lt;interface_name&gt;</code></pre><p>원하는 인터페이스 이름을 입력하면, 각 인터페이스 형태 및 인터페이스 이름을 확인할 수 있다. 예를 들면 다음과 같다.</p>
<pre><code>$ ros2 interface show geometry_msgs/msg/Twist
Vector3 linear
Vector3 angular</code></pre><p>이 외 명령어들은 다음 것들이 있다.</p>
<ul>
<li>ros2 interface list : 모든 msg, srv, action 메시지를 시현</li>
<li>ros2 interface packages : 인터페이스를 담고 있는 패키지 목록 시현</li>
<li>ros2 interface package : 패키키명을 입력시 지정한 패키지에 포함한 인터페이스 시현</li>
<li>ros2 interface proto : 특정 인터페이스 형태(이름) 입력시 그 인터페이스 기본 형태 시현</li>
</ul>
<h3 id="msg-srv-action-형태의-차이">msg, srv, action 형태의 차이</h3>
<p>먼저 토픽에 사용되는 msg를 살펴보면 다음과 같다.</p>
<pre><code>fieldtype fieldname</code></pre><p>그 다음으로, 서비스에 사용되는 srv에 대해서 살펴보면 다음과 같다.</p>
<pre><code>service_type service_request
---
service_type service_result</code></pre><p>서비스는 요청과 응답으로 구성되기 때문에, 구분자(---)에 의해서 분리되어 출력되게 된다. 예를 들면 다음과 같다.</p>
<pre><code>$ ros2 interface show turtlesim/srv/Spawn.srv
float32 x
float32 y
float32 theta
string name
---
string name</code></pre><p>이번에는 액션에 대해서 살펴보자.
액션 같은 경우 action 인터페이스를 사용하게 된다.
액션의 경우 액션 목표(goal), 중간값에 해당하는 액션 피드백(feedback), 그리고 최종 결과인 액션 결과(result)로 나누어서 표기하게 된다.
특이하게, 결과를 중간에 표기하고 피드백을 마지막에 표기한다.</p>
<pre><code>action_type action_goal
---
action_type action_result
---
action_type action_feedback</code></pre><p>예시를 들면 다음과 같다.</p>
<pre><code>$ ros2 interface show turtlesim/action/RotateAbsolute.action
float32 theta
---
float32 delta
---
float32 remaining</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] 토픽, 서비스, 액션, bag]]></title>
            <link>https://velog.io/@hy_k/ROS2-%ED%86%A0%ED%94%BD-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%A1%EC%85%98-bag</link>
            <guid>https://velog.io/@hy_k/ROS2-%ED%86%A0%ED%94%BD-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%A1%EC%85%98-bag</guid>
            <pubDate>Tue, 10 Sep 2024 15:09:13 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 토픽, 서비스, 액션을 알아보고, 관련한 명령어들을 공부해보도록 하자.</p>
<hr>
<h1 id="토픽">토픽</h1>
<blockquote>
<p>💡 ** 토픽 **
<img src="https://velog.velcdn.com/images/hy_k/post/7c2b5011-8ece-4210-919e-685f1394a8c1/image.png" alt="">
출처링크: <a href="https://cafe.naver.com/openrt/24101">https://cafe.naver.com/openrt/24101</a>
위 그림처럼, 비동기식 ** 단방향 ** 메시지 송수신 방식을 토픽이라고 한다. msg 인터페이스 형태의 메시지를 퍼블리시 하는 ** 퍼블리셔(publisher) ** 와 메시지를 서브스크라이브 하는 ** 서브스크라이버(subscriber) ** 간의 통신이다.
노드들은 하나 이상의 토픽을 퍼블리시 할 수도 있고, 서브스크라이브 할 수도 있다.</p>
</blockquote>
<h3 id="토픽-관련-명렁어">토픽 관련 명렁어</h3>
<h4 id="토픽-목록-확인">토픽 목록 확인</h4>
<pre><code>$ ros2 topic list
// 현재 개발 중인 모든 노드들의 토픽 정보 확인

$ ros2 topic list -t
// 토픽들의 각 메시지 형태(type)도 같이 표기해줌
// 예를 들어서, [geometry_msgs/msg/Twist] 이런 식으로</code></pre><p>토픽을 퍼블리시 하고, 서브스크라이브 하는 것은 다음과 같이 확인 가능하다.</p>
<pre><code>$ rqt_graph</code></pre><p><img src="https://velog.velcdn.com/images/hy_k/post/1c2f4a00-9277-4de2-8082-73748a2c67bc/image.png" alt="">
해당 이미지는 rqt_graph로 확인하여, Dead sinks와 Leaf topics를 해제하여 모든 토픽을 출력하게 만든 이미지이다.</p>
<h4 id="토픽-정보-확인">토픽 정보 확인</h4>
<pre><code>$ ros2 topic info &lt;topic_name&gt;
// 토픽 메시지 형태, 퍼블리셔 및 서브스크라이 출력</code></pre><h4 id="토픽-내용-확인">토픽 내용 확인</h4>
<pre><code>$ ros2 topic echo &lt;topic_name&gt;
// 토픽 내용 출력
// 모든 메시지는 SI 단위계를 기준</code></pre><h4 id="토픽-대역폭-확인">토픽 대역폭 확인</h4>
<pre><code>$ ros2 topic bw &lt;topic_name&gt;
// 메시지의 대역폭, 즉 토픽 메시지의 크기를 알 수 있음</code></pre><h4 id="토픽-전송-주기-확인">토픽 전송 주기 확인</h4>
<pre><code>$ ros2 topic hz &lt;topic_name&gt;</code></pre><h4 id="토픽-지연-시간-확인">토픽 지연 시간 확인</h4>
<pre><code>$ ros2 topic delay &lt;topic_name&gt;
// latency(지연시간) 확인이 가능하다</code></pre><h4 id="토픽-퍼블리시">토픽 퍼블리시</h4>
<pre><code>$ ros2 topic pub &lt;topic_name&gt; &lt;msg_type&gt; &quot;&lt;args&gt;&quot;
// --once 옵션시 1번만 발행, topic_name 앞에 작성
$ ros2 topic pub --once ~
// --rate 옵션으로 주기를 Hz 단위로 지정 가능
$ ros2 topic pub --rate 1 ~
// 1Hz로 지정</code></pre><h3 id="bag-기록하기">bag 기록하기</h3>
<blockquote>
<p>💡 ** ros2 bag이란? **
퍼블리시 되는 토픽을 파일 형태로 저장하고, 필요할 때 불러들여서 다시 재생할 수 있는 기능을 rosbag이라고 한다. 이는 복잡한 알고리즘을 검증할 때 유용하게 사용할 수 있다.</p>
</blockquote>
<h4 id="bag-파일-기록하기">bag 파일 기록하기</h4>
<pre><code>$ ros2 bag record &lt;topic_name1&gt; &lt;topic_name2&gt; ..
// 원하는 이름이 있을 경우 -o 옵션을 통해 지정 가능</code></pre><h4 id="bag-정보-출력">bag 정보 출력</h4>
<pre><code>$ ros2 bag info &lt;bag_name&gt;</code></pre><p>bag 파일에 대한 타임 스탬프, 토픽 이름/메시지 형태/메시지 별 개수와 총 메시지 개수 등이 정보가 출력된다.</p>
<h4 id="bag-재생하기">bag 재생하기</h4>
<pre><code>$ ros2 bag play &lt;bag_name&gt;</code></pre><hr>
<h1 id="서비스">서비스</h1>
<blockquote>
<p>💡 ** 서비스란? **
<img src="https://velog.velcdn.com/images/hy_k/post/f676d7f5-01d3-4670-bd6a-4e75b9fe6f5e/image.png" alt="">
출처링크: 상동
** 동기식 양방향 ** 메시지 송수신 방식을 서비스(service)라고 하며, 서비스 요청(request)을 하는 서비스 클라이언트(service client), 서비스 요청을 받아 서비스를 수행한 후 응답(response)하는 서비스 서버(service server)가 있다. 토픽이 msg 인터페이스를 사용하는 것처럼 서비스도 srv 인터페이스를 사용한다.
동일한 서비스 서버에 대해 복수의 서비스 클라이언트가 가질 수 있도록 설계되었다.</p>
</blockquote>
<h3 id="서비스-관련-명령어">서비스 관련 명령어</h3>
<h4 id="서비스-목록-확인">서비스 목록 확인</h4>
<pre><code>$ ros2 service list
// -t 옵션시 서비스 인터페이스도 같이 출력</code></pre><h4 id="서비스-형태-확인">서비스 형태 확인</h4>
<pre><code>$ ros2 service type &lt;service_name&gt;
// 여기서 형태라함은 srv 인터페이스를 의미한다.</code></pre><h4 id="서비스-찾기">서비스 찾기</h4>
<pre><code>$ ros2 service find &lt;service_interface&gt;
// 해당 인터페이스를 사용하는 서비스 탐색이 가능하다</code></pre><h4 id="서비스-요청">서비스 요청</h4>
<pre><code>$ ros2 service call &lt;service_name&gt; &lt;service_type&gt; &quot;&lt;args&gt;&quot;</code></pre><p>서비스 서버에게 서비스 요청을 줄 수 있는 명령어이다. 서비스명, 서비스 형태, 서비스 요청 내용을 기술하여 서비스 요청을 하면 된다.</p>
<hr>
<h1 id="액션">액션</h1>
<blockquote>
<p>💡 ** 액션이란? **
<img src="https://velog.velcdn.com/images/hy_k/post/62c5e7d7-c736-4f95-b080-04def70f01aa/image.png" alt="">
출처링크: <a href="https://cafe.naver.com/openrt/24142">https://cafe.naver.com/openrt/24142</a>
액션(action)은 ** 비동기식, 동기식 양방향 메시지 송수신 방식 ** 으로, 액션 목표(goal)를 지정하는 액션 클라이언트(action client)와 액션 목표를 받아 특정 태스크를 수행하며 중간결과를 전송하는 액션 피드백(action feedback) 그리고 최종 결과를 담은 액션 결과(action result)을 전송하는 액션 서버(action server)의 조합으로 이루어져 있다. 마치 토픽과 서비스가 적절히 융합되어 있다고 볼 수 있는 메시지 통신이다. 액션은 action 인터페이스를 사용한다.</p>
</blockquote>
<blockquote>
<p>💡 ** ROS1과 ROS2의 액션의 차이 **
ROS1에서는 목표, 피드백, 결과를 토픽으로만 사용하였으나, ROS2에서는 목표 전달/목표 취소/결과 받기를 서비스 통신을 사용한다. 동기식 통신을 통해 원하는 타이밍에 적절한 액션을 수행하기 위해서이다. 목표 상태는 목표값을 전달한 후 상태 머신(state machine)을 구동하여 액션 프로세스를 추종하는 것이다.</p>
</blockquote>
<h3 id="액션-관련-명령어">액션 관련 명령어</h3>
<h4 id="액션-목록">액션 목록</h4>
<pre><code>$ ros2 action list -t
// 현재 실행 중인 액션 목록 확인</code></pre><h4 id="액션-정보">액션 정보</h4>
<pre><code>$ ros2 action info &lt;action_name&gt;</code></pre><p>액션 이름과 액션 서버, 클라이언트 노드 이름 및 개수를 확인 가능하다.</p>
<h4 id="액션-목표-전달">액션 목표 전달</h4>
<pre><code>$ ros2 action send_goal &lt;action_name&gt; &lt;action_type&gt; &quot;&lt;value&gt;&quot;</code></pre><p>액션 이름, 형태, 목푯값을 전달하면 된다.
만약 피드백을 포함해서 화면에 출력하고 싶드면, 마지막에 --feedback 옵션을 추가해주면 된다.</p>
<hr>
<h1 id="토픽-서비스-액션-비교">토픽, 서비스, 액션 비교</h1>
<p><img src="https://velog.velcdn.com/images/hy_k/post/c596bdbd-6e05-46b9-8834-ceadda330dc8/image.png" alt="">
출처링크:<a href="https://cafe.naver.com/openrt/24154">https://cafe.naver.com/openrt/24154</a></p>
<p>그리고 뒤에서 다룰 내용이지만,</p>
<ul>
<li>토픽에 사용되는 msg 인터페이스</li>
<li>서비스에 사용되는 srv 인터페이스</li>
<li>액션에 사용되는 action 인터페이스</li>
</ul>
<p>이 3가지를 비교하면 다음과 같다.
<img src="https://velog.velcdn.com/images/hy_k/post/ba6e0bfe-8996-49c2-b686-0ad6e53dfe4a/image.png" alt="">
출처링크: <a href="https://cafe.naver.com/openrt/24154">https://cafe.naver.com/openrt/24154</a></p>
<p>인터페이스에 대해서는 바로 다음 게시글에 다룰 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] 노드와 데이터 통신]]></title>
            <link>https://velog.io/@hy_k/ROS2-%EB%85%B8%EB%93%9C%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@hy_k/ROS2-%EB%85%B8%EB%93%9C%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Mon, 09 Sep 2024 22:44:20 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS2의 노드와, 노드의 데이터 통신 그리고 노드 관련 명령어에 대해서 알아볼 것이다.</p>
<hr>
<h1 id="노드와-메시지-통신">노드와 메시지 통신</h1>
<blockquote>
<p>💡 ** 노드 **
노드(node)는 최소 단위의 실행 가능한 프로세스를 가리키는 용어로, ROS에서 최소한의 실행 단위로 기능하게 된다. 각 노드의 역할을 목적에 맞추어 세분화 시켜 각 노드들 간의 의존성을 줄이고, 독립성을 높여 재사용성을 강화하게 된다.</p>
</blockquote>
<blockquote>
<p>💡 ** 메시지 통신 **
ROS 시스템에서는 수많은 노드들이 작동하므로, 노드 사이의 데이터 입출력이 피룡하다. 여기서 주고 받는 데이터를 메시지(Message)라고 하며, 메시지를 주고받은 통신 방식을 메시지 통신(Message Communication)이라고 한다.</p>
</blockquote>
<blockquote>
<p>💡 ** 메시지 **
정수형, 실수형, 문자열과 같은 데이터 형태이며, 그 외에 간단한 자료구조 혹은 배열을 사용할 수 있다. 메시지를 주고 받는 방식에 따라서 토픽(topic), 서비스(service), 액션(action)으로 구분이 가능하다.</p>
</blockquote>
<hr>
<h1 id="노드-관련-명령어">노드 관련 명령어</h1>
<h3 id="실행-명령어">실행 명령어</h3>
<pre><code>$ ros2 run &lt;pkg_name&gt; &lt;node_name&gt;
// 하나의 노드를 실행 가능함

$ ros2 run &lt;pkg_name&gt; &lt;node_name&gt; __node:=&lt;new_name&gt;
// 이런 식이면 동일한 노드를 서로 다른 이름으로 실행이 가능함
// 혹은 namespace를 변경하면 된다.</code></pre><pre><code>$ ros2 launch &lt;pkg_name&gt; &lt;launch_file_name&gt;
// 복수의 노드를 다양한 설정을 통해서 실행 가능</code></pre><p>이 후 노드 사이의 관계는</p>
<pre><code>$ rqt_graph</code></pre><p>명령어를 통해서 시각화 가능하다.</p>
<h3 id="노드-목록-명령어">노드 목록 명령어</h3>
<pre><code>$ ros2 node list</code></pre><p>현재 ROS2 환경에서 실행 중인 모든 노드가 출력된다.</p>
<h3 id="노드의-정보-출력-명령어">노드의 정보 출력 명령어</h3>
<pre><code>$ ros2 node info &lt;node_name&gt;</code></pre><p>노드명을 통해서 노드의 퍼블리셔, 서브스크라이버, 서비스, 액션, 파라미터 정보를 출력할 수 있는 명령어이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ROS2] DDS 그리고 QoS]]></title>
            <link>https://velog.io/@hy_k/ROS2-DDS-%EA%B7%B8%EB%A6%AC%EA%B3%A0-QoS</link>
            <guid>https://velog.io/@hy_k/ROS2-DDS-%EA%B7%B8%EB%A6%AC%EA%B3%A0-QoS</guid>
            <pubDate>Sun, 08 Sep 2024 07:21:06 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 ROS1과 ROS2의 가장 큰 차이점이라고 할 수 있는 DDS 그리고 DDS에 따른 QoS를 살펴보도록 하자.</p>
<hr>
<h1 id="dds">DDS</h1>
<p>ROS1과 마찬가지로, ROS2에서는 메시지 통신인 토픽, 서비스, 액션(ROS1 같은 경우에는 최소 Noetic은 가야된다), 파라미터가 있다. ROS1을 사용해보았다면 이러한 메시지 통신들 모두 퍼블리시(publish)와 서브스크라이브(subscribe)를 기본적으로 사용함을 알 수 있다.</p>
<p>그런데 ROS Master에 의해서 각 노드들과 메시지 통신이 관리되었고, 이를 위한 통신 프로토콜로써 TCPROS, UDPROS 등을 사용했던 ROS1과 다르게, ROS2는 DDS(Data Distribution Service)의 RTPS(Real Time Publish Subscribe)를 사용하고 있다.</p>
<p><img src="https://velog.velcdn.com/images/hy_k/post/0220477b-2f72-4729-9642-bf8ee2456178/image.png" alt="">
참고 주소: <a href="https://cafe.naver.com/openrt/24031">https://cafe.naver.com/openrt/24031</a></p>
<p>DDS 도입으로 변화한 ROS2의 특징은 다음과 같다.</p>
<blockquote>
<p>💡 ** 변화점 **</p>
</blockquote>
<ol>
<li>IDL(Interface Description Language) 사용을 통한 쉬운 메시지 정의</li>
<li>실시간 데이터 전송 보장</li>
<li>임베디드 시스템에서의 사용</li>
<li>Master 없이 동적 노드 탐색 -&gt; 여러 DDS 노드 간 통신 O</li>
<li>QoS(Quality of Service) 설정을 통한 커스텀 통신 설정</li>
<li>보안성 강화</li>
</ol>
<p>한편, DDS의 정의는 다음과 같다.</p>
<blockquote>
<p>💡 ** DDS의 정의 **</p>
</blockquote>
<ul>
<li>데이터 통신을 위한 미들웨어</li>
<li>미들웨어 프로토콜(DDSI-RTPS)과 같이 DDS 사양을 만족하는 미들웨어 API가 실체</li>
<li>ISO 7계층에서 4~7계층 사이에 해당</li>
</ul>
<blockquote>
<p>📌 ** ISO 7계층이란? **
ISO 7계층은 네트워크 프로토콜의 모델을 의미한다.</p>
</blockquote>
<ol>
<li>물리계층(물리적 매체, 전선 등)</li>
<li>데이터링크 계층(Wi-Fi, 이더넷 등)</li>
<li>네트워크 계층(IP 주소할당, 라우팅, 패킷 전달 등)</li>
<li>전송계층(데이터 분할/전송/재조립, TCP와 UDP의 동작 계층)</li>
<li>세션 계층(통신 세션 설정/유지/종료 기능 등)</li>
<li>표현 계층(데이터 형식 정의, 변환)</li>
<li>웅용 계층(사용자가 네트워크에 접근해 특정 서비스 사용)</li>
</ol>
<p>DDS의 특징은 다음과 같이 정리 가능하다.</p>
<blockquote>
<p>💡 ** DDS 특징 **</p>
</blockquote>
<ol>
<li>산업 표준</li>
<li>운영체제 독립 -&gt; 윈도우, 리눅스, macOS</li>
<li>언어 독립 -&gt; RMW 미들웨어로 추상화 -&gt; RCL에 따라 다양한 언어 지원
<img src="https://velog.velcdn.com/images/hy_k/post/a6ee6df8-e7b5-4760-a6a6-758dbb59d762/image.png" alt="">
출처: <a href="https://github.com/ros2/ros_core_documentation">https://github.com/ros2/ros_core_documentation</a></li>
<li>UDP 기반으로 하되, QoS 설정이 가능함</li>
<li>데이터 중심적 기능</li>
<li>동적 노드 검색</li>
</ol>
<ul>
<li>ROS2의 DDS는 동적 노드 검색을 통해 Master가 필요하지 않다.</li>
<li>ROS2에서는 노드를 DDS의 참여자(participant)로 취급한다.<blockquote>
<blockquote>
<p>📌 ** ROS1의 경우? **
ROS1의 경우 MAster에서 노드에 대한 이름 지정 및 등록 서비스를 제공하였고, 이러한 정보를 토대로 Master를 거쳐 각 노드들끼리 직접 연결되어 통신을 수행하였다.</p>
</blockquote>
</blockquote>
</li>
</ul>
<blockquote>
<ol start="7">
<li>쉽게 확장 가능한 아키텍쳐</li>
<li>다양한 벤더를 지원하지만, 그럼에도 상호 운용성 보장</li>
<li>QoS 설정(이에 대해서는 후술)</li>
<li>강력한 보안 기능: 산업계 표준인 DDS 보안 사양 도입</li>
</ol>
</blockquote>
<p>기본적으로 ROS2에서 어떤 노드들을 실행해 메시지 통신을 주고받는다는 것 자체가 이미 DDS 통신을 사용하고 있는 것이다.</p>
<h3 id="1-rmw-변경-방법">1. RMW 변경 방법</h3>
<blockquote>
<p>📌 ** RMW 변경 방법 **
RMW를 바꾸고 싶다면, RMW_IMPLEMENTATION 환경변수로 원하는 RMW를 지정한 후 노드를 실행하면 된다.</p>
</blockquote>
<pre><code>$ export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp</code></pre><p>지원하는 RMW에는 다음 등이 있다.
**</p>
<ol>
<li>rmw_connext_cpp</li>
<li>rmw_cyclonedds_cpp</li>
<li>rmw_fastrtps_cpp</li>
<li>rmw_gurumdds_cpp</li>
</ol>
<p>**
이렇게 RMW를 바꾸어도 상호운용성이 보장된다.</p>
<h3 id="2-domain-변경">2. Domain 변경</h3>
<blockquote>
<p>💡 ** Domain 변경 방법 **
ROS2에서는 기본적으로 UDP 멀티캐스트로 통신이 이루어지기 때문에 별도 설정 없이는 동일 네트워크 상의 모든 노드가 연결된다. 이를 방지하기 위해선
**</p>
</blockquote>
<ol>
<li>서로 다른 네트워크 사용</li>
<li>ROS namespace 추가</li>
<li>DDS Domain 변경</li>
</ol>
<p>**
이 있다.
이 중 Domain 변경이 제일 쉬운데, ROS_DOMAIN_ID 환경변수를 지정해주면 되기 때문이다. 같은 ROS_DOMAIN_ID를 가지고 있는 노드들끼리만 연결될 수 있다.</p>
<pre><code>$ export ROS_DOMAIN_ID=11</code></pre><hr>
<h1 id="qos">QoS</h1>
<p>이번에는 DDS의 QoS에 대해서 알아보자.</p>
<blockquote>
<p>💡 ** QoS **
QoS는 쉽게 말하면 DDS 데이터 통신의 옵션이다.
총 22가지 지정 가능한 항목이 있는데 이 중 ROS2에서는 다음 6가지를 많이 사용한다.
**</p>
</blockquote>
<ol>
<li>Reliability</li>
<li>History</li>
<li>Durability</li>
<li>Deadline</li>
<li>Lifespan</li>
<li>Liveliness</li>
</ol>
<p>**
<img src="https://velog.velcdn.com/images/hy_k/post/8894820a-6939-4ef6-9d52-d46c15c4725e/image.png" alt="">
QoS의 22가지 옵션들.
참고링크: <a href="https://cafe.naver.com/openrt/24319">https://cafe.naver.com/openrt/24319</a></p>
<p>이제 ROS2에서 사용하는 QoS 옵션들에 대해서 조금 더 자세히 알아보자.</p>
<h3 id="1-history">1. History</h3>
<blockquote>
<ol>
<li>지정 가능한 옵션</li>
</ol>
</blockquote>
<ul>
<li>** KEEP_LAST ** : 정해진 메시지 큐 크기만큼만 메시지 보관(depth 매개변수를 통해서 메시지 큐의 크기를 지정)</li>
<li>** KEEP_ALL ** : 모든 데이터를 보관</li>
</ul>
<ol start="2">
<li>RxO(Requested By Offered)
없음</li>
<li>사용 예시<pre><code>rclcpp::QoS(rclcpp::KeepLast(10)); // rclcpp
qos_profile = QosProfile(history=QosHistoryPolicy.KEEP_LAST, depth=10) 
# rclpy</code></pre></li>
</ol>
<h3 id="2-reliability">2. Reliability</h3>
<blockquote>
<ol>
<li>지정 가능한 옵션</li>
</ol>
</blockquote>
<ul>
<li>** BEST_EFFORT ** : 데이터 송신에 집중, 전송 속도 중시, 데이터 유실 가능</li>
<li>** RELIABLE ** : 데이터 수신에 집중, 신뢰성 중시, 유실 발생시 재전송을 통해 유실 보완</li>
</ul>
<ol start="2">
<li>RxO
<img src="https://velog.velcdn.com/images/hy_k/post/0e1d46eb-11cb-423e-8461-9ff246be8a93/image.png" alt="">
링크: <a href="https://cafe.naver.com/openrt/24319">https://cafe.naver.com/openrt/24319</a></li>
<li>예시<pre><code>rclcpp::QoS(rclcpp::KeepAll).best_effort(); // rclcpp
qos_profile = QosProfile(reliability=QosReliability.BEST_EFFORT)
# rclpy</code></pre></li>
</ol>
<h3 id="3-durability">3. Durability</h3>
<blockquote>
<ol>
<li>지정 가능한 옵션
데이터를 수신하는 서브스크라이버가 생성되기 전의 데이터를 사용할 것인지 대한 QoS 옵션</li>
</ol>
</blockquote>
<ul>
<li>** TRANSIENT_LOCAL ** : subscription이 생기기 전의 데이터 보관</li>
<li>** VOLATILE ** : subscription이 생성되기 전의 데이터는 무효</li>
</ul>
<ol start="2">
<li>RxO
<img src="https://velog.velcdn.com/images/hy_k/post/ef3c8d0d-dc0b-4d5a-a884-3b197a61e1b2/image.png" alt="">
링크: 상동</li>
<li>예시<pre><code>rclcpp::QoS(rclcpp::KeepAll).transient_local(); // rclcpp
qos_profile = QosProfile(durability = QosDurabilityPolicy.TRANSIENT_LOCAL)
# rclpy</code></pre></li>
</ol>
<h3 id="4-deadline">4. Deadline</h3>
<blockquote>
<ol>
<li>지정 가능한 옵션
정해진 주기 안에 데이터가 발신 및 수신되지 않을 경우 EventCallback을 실행시키는 QoS 옵션</li>
</ol>
</blockquote>
<ul>
<li>** deadline_duration ** : deadline을 확인하는 주기</li>
</ul>
<ol start="2">
<li>RxO
<img src="https://velog.velcdn.com/images/hy_k/post/734bf678-9603-4df6-982b-7378a6a22f2b/image.png" alt="">
링크: 상동</li>
<li>예시<pre><code>rclcpp::QoS(10).deadline(100ms); // rclcpp
qos_profile = QosProfile(depth=10, deadline=Duration(0.1))
# rclpy</code></pre></li>
</ol>
<h3 id="5-lifespan">5. Lifespan</h3>
<blockquote>
<ol>
<li>지정 가능한 옵션
정해진 주기 안에서 수신되는 데이터만 유효 판정하고, 그렇지 않은 데이터는 삭제하는 QoS 옵션</li>
</ol>
</blockquote>
<ul>
<li>** lifespan_duration ** : LifeSpan을 확인하는 주기</li>
</ul>
<ol start="2">
<li>RxO
해당 없음</li>
<li>사용 예시<pre><code>rclcpp::QoS(10).reliable().transient_local().lifespan(10ms);
//rclcpp
qos_profile = QoSProfile(lifespan = Duration(0.01))
# rclpy</code></pre></li>
</ol>
<h3 id="6-liveliness">6. Liveliness</h3>
<blockquote>
<ol>
<li>지정 가능한 옵션
정해진 주기 안에서 노드 혹은 토픽의 생사를 확인하는 QoS 옵션</li>
</ol>
</blockquote>
<ul>
<li>** liveliness ** : 자동 또는 매뉴얼로 확인할지를 지정하는 옵션, AUTOMATIC, MANUAL_BY_NODE, MANUAL_BY_TOPIC 중 선택<blockquote>
<blockquote>
<ul>
<li>automatic : 생존성을 관리하는 주체가 DDS 미들웨어</li>
<li>manual : 생존성을 관리하는 주체가 사용자</li>
</ul>
</blockquote>
</blockquote>
</li>
<li>** lease_duration ** : liveliness를 확인하는 주기</li>
</ul>
<ol start="2">
<li>RxO
<img src="https://velog.velcdn.com/images/hy_k/post/91f77ce0-dd24-41e4-ace8-ea444bfaeedc/image.png" alt="">
출처: 상동</li>
<li>사용예시<pre><code>// rclcpp
rclcpp::QoS qos_profile(10);
qos_profile
.liveliness(RMW_QOS_POLICY_LIVELINESS_AUTOMATIC)
.liveliness_lease_duration(1000ms);</code></pre><pre><code># rclpy
qos_profile = QosProfile(
 liveliness=AUTOMATIC,
 liveliness_lease_duration = Duration(1.0))</code></pre></li>
</ol>
<p>이러한 QoS를 기반으로, RMW에서는 QoS 설정을 쉽게 사용할 수 있도록 가장 많이 사용하는 QoS 설정을 세트(set)로 표현해둔 것이 있는데 이를 RMW QoS Profile이라고 한다.
<img src="https://velog.velcdn.com/images/hy_k/post/37d49e8c-453f-4e0d-aa51-92f5f40ef546/image.png" alt="">
출처: <a href="https://cafe.naver.com/openrt/24319">https://cafe.naver.com/openrt/24319</a></p>
<p>파이썬에서 이러한 세트를 사용하기 위해서는 다음과 같이 import 해야한다.</p>
<pre><code>from rclpy.qos import qos_profile_sensor_data</code></pre><p>사용자가 직접 새로운 qos_profile을 설정해서 사용도 가능하다. 파이썬에서는 다음과 같이 import 하고 지정하면 된다.</p>
<pre><code>from rclpy.qos import QoSDurabilityPolicy
from rclpy.qos import QoSHistoryPolicy
from rclpy.qos import QoSProfile
from rclpy.qos import QoSReliabilityPolicy</code></pre><p>예를 들어보면 다음과 같다.</p>
<pre><code>QOS_RKL10V = QoSProfile(
    reliability = QoSReliabilityPolicy.RELIABLE,
    history = QoSHistoryPolicy.KEEP_LAST,
    depth = 10,
    durability = QoSDurabilityPolicy.VOLATILE)</code></pre><p>이후 다음과 같이 사용하면 된다.</p>
<pre><code>self.sensor_publisher = self.create_publisher(Int8MultiArray, &#39;sensor&#39;, QOS_RKL10V)</code></pre>]]></description>
        </item>
    </channel>
</rss>