ROS

[ ROS ] ROS를 이해해보자 #2

life_ash 2021. 3. 18. 14:47
Node들이 서로 Topic을 주고 받을 수 있게 하는 객체들은 Publisher Subscriber이다.

 

0. 머리말

지난 번 'ROS를 이해해보자 #1'에서는 ROS가 어떠한 장점(프로세스 간 송수신)을 지니고 있는지 알아보았다. 개인적으로는, ROS가 왜 쓰이는지 파악하는게 ROS의 정의를 읽는 것보다 ROS를 이해하는데 더 도움된다고 생각한다. 그래서, 첫 글에서는 ROS의 장점에 대해 알아보고, 독자로 하여금 ROS를 왜 사용하는지 자연스럽게 이해시켜주고 싶었다.

 

ROS를 사용해보고 싶은 사람에게, 어떤 목적으로 쓰는 지 알려주면 목적에 맞는 ROS의 도구들을 잘 찾고 활용할 것 같아서..

나는 무턱대고 인터넷에 돌아다니는 튜토리얼과 형식적인 정의들을 보며 ROS를 시작했으나, 매우 비효율적이었다.

그래서, 이런 방향으로 ROS를 배우기 시작하면 어떨까 싶어서 글을 작성하고 있다. 도움이 되길 :)

 

아무튼, 이번에는 이어서, ROS가 데이터를 주고 받는 세 가지 방법(Topic, Service, Action)중에서 Topic에 대해 알아보자. 일단 Topic을 이해하기에 앞서, 그 전에 미리 알아둬야 할 용어와 내용이 있다.

 

최대한 쉽고 간결하게 설명해보겠다.

1. ROS 용어

 

지난 글에서, ROS는 프로세스 간 데이터 송수신이 특징이자 장점이라고 설명했다. 여기서, ROS에서 프로세스는 Node (노드) 라고 부른다. 즉, Node들이 서로 데이터를 주고 받게 된다. 그리고, 하나 이상의 Node (노드) 들과 빌드를 위한 정보들이 포함되어 있는 것을 Package (패키지) 라고 한다. 마지막으로, ROS에서 주고 받는 데이터는 Message (메세지) 라고 한다.

 

더 많은 내용들이 있지만, 여기까지만 하고 다음 글에서 더 세세하게 다뤄보도록 하겠다.

 

용어에 대해 더 자세히 알고 싶으면 아래의 링크를 참고하길 바란다. 필수적인 내용들이 잘 정리되어있다.

jungmonster.tistory.com/154

 


 

2. Topic

Topic을 가장 먼저 설명하는 이유는 ROS에서 제일 많이 사용하는 통신 방법이기 때문이다.

 

우선, ROS에서는 PublisherSubscriber라는 객체(Object)를 통해 Node들이 서로 Topic을 주고 받을 수 있게 한다.

 

Publisher는 Topic을 송신하는 객체, Subscriber는 Topic를 수신하는 객체이다.

Publisher는 코드 상에서 다음과 같은 형태로 찾아 볼 수 있다.

ros::init(argc, argv, "talker");
ros::NodeHandle n;

// Publisher
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

 

위 코드의 첫번째 줄은 'talker'라는 이름의 노드를 시작하겠다는 줄이며, 두번째 줄은 해당 노드를 관리할 객체(ros::NodeHandle n) 를 선언한 것이다. 마지막 줄은 Publisher 객체를 선언하고, 'chatter'라는 이름의 topic(메세지 타입은 std_msgs::String(문자열))을 송신하겠다는 뜻이다. 그리고, 1000은 queue_size를 의미하는데, queue_size는 저장하고자 하는 최대 데이터 갯수를 의미한다.

 

이렇게 객체를 선언하고, 우리는 아래와 같은 코드를 통해 'chatter'라는 이름의 topic을 송신할 수 있다.

std_msgs::String msg;

std::stringstream ss;
ss << "Hello, world" << count;

msg.data = ss.str();

chatter_pub.pub(msg)

가장 마지막 줄(chatter_pub.pub(msg))이 "Hello, world"라는 std_msgs::String 타입의 메세지를 topic으로 송신하는 줄이다.


Subscriber는 코드 상에서 다음과 같은 형태로 찾아 볼 수 있다.

ros::init(argc, argv, "listener");
ros::NodeHandle n;

// Subscriber
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

 

위 코드의 첫번째 줄은 'listener'라는 이름의 노드를 시작하겠다는 줄이며, 두번째 줄은 해당 노드를 다룰 객체를 선언한 것이다.

 

마지막 줄은 Subscriber 객체를 선언하고, 'chatter'라는 이름의 topic을 수신하겠다는 뜻이다. 여기서

1000은 queue_size를 의미한다. chatterCallback은 'chatter'라는 이름의 topic을 수신하였을 때, 자동으로 실행하는 함수이다.

 

예를 들면, chatterCallback 함수는 다음과 같이 정의할 수 있다.

// chatterCallback 함수

void chatterCallback(const std_msgs::String::ConstPtr& msg) {
	ROS_INFO("I heard: [%s]", msg->data.c_str());
}

 

chatterCallback 함수의 파라미터인 const std_msgs::String::ConstPtr& msg는 수신한 topic을 가르키는 포인터이다.

 

따라서, Publisher가 있는 Node에서 "Hello, world"의 메세지를 topic으로 송신하였으므로, Subscriber가 있는 Node에서는 이를 수신하여, chatterCallback 함수에서 "I heard: [Hello, world]" 를 출력하게 된다.

 


3. Topic의 통신 방식

 

이 부분은 잘 몰라도 된다. ROS 사용하는데 큰 문제 없다...

 

그렇다면, ROS에서는 Topic을 어떻게 주고 받을까? 아래의 사진은 ROS에서 Topic을 송수신하는 방법을 나타내고 있다.

 

여기서 중요하게 짚고 넘어갈 만한 부분은 Talker와 Listener가 ROS Master를 통해 송수신하고 있다는 점이다.

 

( Talker: Publisher가 있는 Node, Listener: Subscriber가 있는 Node )

 

솔직히 이 부분은 몰라도, ROS를 사용할 때 큰 불편함은 없는 것 같다.

번호를 따라서 보면, 0) publisher에서 "bar" topic을 송신하겠다고 하고, 1) subscriber에서 "bar" topic을 수신하겠다고 하며,
2)... 3)... 4)... 5)... 6)... XML-RPC 주소를 받고, 연결 요청하고, 확인하고, 연결하고, 데이터를 보낸다는데,,, 자세히는 모르겠다... :)

그런데 처음 공부할 때, 이거만 자세히 들여다보고 있었으니 당연히 와닿는게 없을 수 밖에...

 

아무튼 이러하다 보니, Topic의 통신 방식은 Publisher에서 송신해서 Subscriber에서 수신만 하는 단방향 통신이고, 비동기(asynchronous) 방식이라, 데이터를 주고 받기 적합한 통신 방식이다. 그래서, 보통 센서 데이터를 Topic으로 주고 받으며, ROS에서 메세지를 통신하는 방식으로 가장 많이 쓰인다.

 


4. 마무리

이 글은 ROS wiki의 'Writing a Simple Publisher and Subscriber' 튜토리얼을 바탕으로 작성되었다. 본 글에 나오는 예시 코드들도 모두 해당 튜토리얼에서 기반하였으니, 위의 튜토리얼을 통해 직접 Topic을 Publish/Subscribe 해보는 노드를 만들어보길 바란다. 다음 글에서는 튜토리얼을 직접 경험해보는 시간을 가져보자.

 

5. References

[1] wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29