본문 바로가기

& 프로그래밍/& ROS

ROS 메시지 통신 이해하기

ROS에서 사용되는 통신 시스템은 메시지(msg), 액션(action), 서비스(svc) 크게 3가지로 이해할 수 있다

이중에서 오늘은 메시지통신이 실제로 어떻게 구현되는지 실습과 함께 이해해보려고 한다

 

메시지통신을 이해하기 전 우선 관련 용어를 이해해보자

 

메시지(message)

노드와 노드가 서로 데이터를 주고 받을때 사용되는 것이 바로 메시지이다. 메시지는 int, float, point, boolean과 같은 변수 형태를 띄고 있으며, 단방향 메시지 송수신 방식의 토픽(topic)과 양방향 메시지 요청(request)/응답(response) 방식의 서비스를 이용한다

 

토픽(topic)

토픽은 단어 뜻 그대로 주제, 이야깃거리를 의미한다. 퍼블리셔(publisher)와 서브스크라이버(subscriber)가 데이터를 주고받으려면 상호가 연결될 주제가 필요하다. 퍼블리셔가 하나의 토픽을 마스터에 등록한 후 토픽내용을 송신하면, 서브스크라이버가 마스터에 등록된 토픽에 정보를 받아 퍼블리셔와 직접 연결하여 데이터를 주고 받는 것이다.

 

이전 용어정리편에서도 예시를 들었는데 다시한번 설명을 하자면,

- 유튜브 본사와 A라는 채널과 B라는 구독자가 있다고 가정하자.

- A채널은 스포츠 정보를 가지고 채널을 게시했다. 그러면 유튜브본사에서는 A채널은 스포츠분야로 구분해서 관리를 하게 된다.

- B구독자는 스포츠 정보가 필요한 사람이다. 그래서 관련 정보를 얻기위해 유튜브에서 검색을해서 A채널을 찾고 구독을 누른다. 

- 이제 B구독자는 A채널이 송신한 스포츠 영상을 직접 수신하여 정보를 얻을 수 있게 된다.

여기서 유튜브본사는 '마스터'를 의미하고, A채널은 '퍼블리셔', B구독자는 '서브스크라이버'가 되는 것이다. 한방에 이해 ok?

 

자, 이제 어느정도 용어에 대해 이해가 되었으니까 직접 퍼블리셔와 서브스크라이버 노드를 작성하고 실행해보도록 하자

 

1. 패키지 생성

기본적인 언어는 c/c++을 이용한다. 토픽을 주고받는 상황을 구현하기위해 관련 패키지를 먼저 생성해준다.

cd ~/catkin_ws/src
catkin_create_pkg ros_tutorial_topic message_generation std_msgs roscpp

위 코드를 해석하자면, 패키지 생성을 뜻하는 catkin_create_pkg를 입력해주고,

자신이 원하는 패키지 이름을 넣어준다(ros_tutorial_topic).

message_generation은 새로운 메시지를 생성할 때 사용되는 패키지로서 새로운 패키지를 생성하기전에 미리 설치해야한다는 의미로, std_msgs와 roscpp을 선행 설치토록 명령하는 것이다. 

std_msgs : ROS 표준 메시지 패키지

roscpp : ROS에서 c++언어를 사용하겠다는 의미를 가진 클라이언트 라이브러리

 

2. 메시지 파일 작성

다음으로 우리가 사용할 메시지를 작성해준다.

$ cd ros_tutorial_topic #패키지 폴더 이동
$ mkdir msg             # 패키지에 msg폴더 생성
$ cd msg                # 생성한 폴더로 이동
$ gedit MsgTutorial.msg # 파일 신규작성

 

신규로 작성한 파일에 아래와 같이 입력해준다

time stamp
int32 data

time 메시지 형식에 stamp변수와 int32 메시지 형식에 data를 생성한 것이다

 

3. 퍼블리셔 노드 생성

이번에는 데이터를 송신하는 퍼블리셔 노드를 생성한다. 위 예시를 들었던 것처럼 유튜브 채널을 만드는 셈이다.

$ roscd ros_tutorial_topic/src  # 패키지의 소스 폴더인 src로 이동
$ gedit park_publisher.cpp     # 소스 파일 신규작성

신규작성한 cpp파일에 다음과 같이 입력해준다

#include "ros/ros.h"  //ROS 기본 헤더파일을 포함
#include "ros_tutorial_topic/MsgTutorial.h" // MsgTutorial 메시지 헤더파일

int main(int argc, char **argv) // 노드 메인함수 생성
{
	ros::init(argc, argv, "topic_publisher"); // 노드명 초기화
    ros::NodeHandle nh; //ROS시스템과 통신을 위해 노드 핸들을 nh로 선언
    ros::Publisher ros_tutorial_pub = nh.advertise<ros_tutorial_topic::MsgTutorial>("ros_tutorial_msg", 100);
    //퍼블리셔를 선언하고 ros_tutorial_topic패키지의 Msgtutorial 메시지 파일을 이용한 퍼블리셔 ros_tutorial_pub을 작성한다
    //토픽 이름은 'ros_tutorial_msg'로 선언하였고, 퍼블리셔 큐사이즈는 100개로 설정하였다
    
    ros::Rate loop_rate(10);  //루프 주기를 설정한다 초당 10Hz를 의미
    
    ros_tutorial_topic::MsgTutorial msg; // MsgTutorial 메시지 파일 형식으로 msg라는 메시지를 선언
    int count=0; //메시지에 사용될 변수를 선언한다
    
    while (ros::ok())
    {
    	msg.stamp = ros::Time::now(); // 현재시간을 msg 안에 있는 stamp에 담아라
        msg.data = count; // count라는 변수를 msg 안에 있는 data에 담아라
        
        ROS.INFO("send msg = %d", msg.stamp.sec); //stamp.sec 메시지를 출력
        ROS.INFO("send msg = %d", msg.stamp.nsec); //stamp.nsec 메시지를 출력
        ROS.INFO("send msg = %d", msg.data); // data메시지를 출력
        
        ros_tutorial_pub.publish(msg) // 메시지를 퍼블리싱한다
        
        loop_rate.sleep(); //위에서 선언한 루프주기에 맞춰 슬립한다
        
        ++count; // count가 1씩 증가
     }
     return 0;
}

 

4. 서브스크라이버 노드 생성

퍼블리셔 노드로 메시지를 보낼 녀석을 만들었으니까 이제 메시지를 받을녀석을 만들어주자

$ gedit park_subscriber.cpp
#include "ros/ros.h"
#include "ros_tutorial_topic/MsgTutorial.h"

void msgCallback(const ros_tutorial_topic::MsgTutorial::ConstPtr&msg)
{
	ROS_INFO("receive msg = %d", msg->stamp.sec);
    ROS_INFO("receive msg = %d", msg->stamp.nsec);
    ROS_INFO("receive msg = %d", msg->data);
}

int main(int argc, char **argv)
{
	ros::init(argc, argv, "park_subscriber")
    ros::NodeHandle nh;
    
    ros::Subscriber ros_tutorial_sub = nh.subscribe("ros_tutorial_msg", 100, msgCallback);
    
    ros::spin();
    
    return 0;
 }

5. 빌드 설정파일 수정

ROS 빌드 시스템 catkin에는 기본적으로 catkin_make가 존재한다. 패키지 폴더 구성을 보면 CMakeLists.txt가 있을텐데, 이것이 바로 패키지 빌드환경을 구성하고 있다. 

$ gedit CMakeLists.txt

텍스트 파일에 들어가면 무수히 많은 문장이 주석 처리되어있는걸 볼 수 있을 것이다. 그 중에서 필요한 코드만 주석을 해제해주면 된다

아래 코드로 작성한 부분을 찾아서 주석을 해제해주고 자신의 퍼블리셔, 서브스크라이버, msg파일 이름에 맞게 변경해주면 된다

find_package(catkin REQUIRED COMPONENTS message_generation std_msgs roscpp)

add_message_files(FILES MsgTutorial.msg)

generate_message(DEPENDENCIES std_msgs)

catkin_package(LIBRARIES ros_tutorial_topic CATKIN_DEPENDS std_msgs roscpp)

include_directories(${catkin_INCLUDE_DIRS})

add_executable(topic_publisher src/topic_publisher.cpp)
add_executable(topic_subscriber src/topic_subscriber.cpp)

add_dependencies(topic_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(topic_subscriber ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(topic_publisher ${catkin_LIBRARIES})
target_link_libraries(topic_subscriber ${catkin_LIBRARIES})

 

6. 노드 빌드 및 실행

자, 이제 모든 준비가 끝났으니 빌드를 해서 테스트를 해보자

$ cd ~/catkin_ws
$ catkin_make

catkin_make를 진행하면 0~100%로 진행상태를 보여준다. 만약 진행상태 중 오류가 발생했다면, 어디가 문제인지 모두 짚어주니까 직접 보면서 오류를 잡아가면 된다

 

roscore

roscore는 반드시 실행시켜주어야 한다. 왜냐? 퍼블리셔와 서브스크라이버를 연결하는 마스터가 필요하니까~

roscore를 실행시켜주고 새로운 터미널창을 열어서 rosrun을 실행시켜준다

# 새로운 창을 먼저 열고

rospack profile
rosrun ros_tutorial_topic topic_publisher

ros_tutorial_topic 패키지에 있는 publisher를 실행시켰다. 이제 서브스크라이버를 실행해서 정상적으로 데이터를 받아오는지 확인하자

# 새로운 창을 열어서

rosrun ros_tutorial_topic topic_subscriber

실행결과는 위 사진과 같다. 가장 상단 터미널이 roscore 명령을 실행시킨 창, 하단 좌측 터미널이 publisher를 실행시켰고, 우측이 subscriber를 실행시킨 모습이다.

publisher가 실시간으로 송신하는 데이터를 subscriber가 바로 받아내는 모습을 볼 수 있다.

 

roscore라는 마스터 안에서 두개의 노드가 실행되고 있는 모습이다. 만약 publisher를 종료한다면 subscriber는 종료된 시점에서 중단된다. 하지만 종료되는 것은 아니다. 데이터 송신이 안되니까 마냥 수신을 기다리고 있는 모습일 뿐, 다시 publisher를 실행시키면 바로 수신하는 모습을 볼 수 있다.

 

만약 pub와 sub가 실행되는 상황에서 마스터 roscore를 꺼버리면?

정답은 pub와 sub에는 어떠한 영향도 없다! roscore 마스터는 토픽을 저장하고 있을 뿐, 실제로 pub와 sub는 서로 다이렉트로 데이터를 주고받기 때문에 첫 연결이 정상적으로 되었다면 이후에는 마스터 역할은 없다

 

여기까지 메시지통신 방법에 대해서 알아보았다. 막연하게 글만 보면 따라하기 급급하고, 이해는 1도 안되겠지만, 실제로 본인 주변에서 가까운 예를 찾아보면서 코드를 쉽게 분석하려 한다면 정말 단순한 과정임을 깨닫게 될 것이다

 

다음번엔 서비스, 액션 통신을 배워보는걸로!