노드는 ROS 네트워크에서 실행가능한 하나의 요소로 publisher node(talker) 생성을 통해 메시지를 계속적으로 broadcast하는 노드를 생성해보도록 하겠다.
우선 앞의 과정에서 생성한 beginner_tutorials 패키지로 이동한다.
1.1 The Code
beginner_tutorials 패키지 경로에서 src 폴더를 생성한다.
이 폴더는 beginner_tutorials 패키지의 모든 소스파일을 저장할 곳이다.
src/talker.cpp 파일을 생성하고 다음의 코드를 붙여넣자.
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/**
* This tutorial demonstrates simple sending of messages over the ROS system.
*/
int main(int argc, char **argv)
{
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line.
* For programmatic remappings you can use a different version of init() which takes
* remappings directly, but for most command-line programs, passing argc and argv is
* the easiest way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, argv, "talker");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
ros::NodeHandle n;
/**
* The advertise() function is how you tell ROS that you want to
* publish on a given topic name. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. After this advertise() call is made, the master
* node will notify anyone who is trying to subscribe to this topic name,
* and they will in turn negotiate a peer-to-peer connection with this
* node. advertise() returns a Publisher object which allows you to
* publish messages on that topic through a call to publish(). Once
* all copies of the returned Publisher object are destroyed, the topic
* will be automatically unadvertised.
*
* The second parameter to advertise() is the size of the message queue
* used for publishing messages. If messages are published more quickly
* than we can send them, the number here specifies how many messages to
* buffer up before throwing some away.
*/
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
/**
* A count of how many messages we have sent. This is used to create
* a unique string for each message.
*/
int count = 0;
while (ros::ok())
{
/**
* This is a message object. You stuff it with data, and then publish it.
*/
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
/**
* The publish() function is how you send messages. The parameter
* is the message object. The type of this object must agree with the type
* given as a template parameter to the advertise<>() call, as was done
* in the constructor above.
*/
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
1.2 The Code Explained
# include "ros/ros.h"
ros/ros.h 는 일반적으로 ROS 시스템에서 사용되는 많은 부분들에 대한 필수적인 헤더를 포함하고 있다.
# include "std_msgs/String.h"
std_msgs 패키지에서 사용되는 std_msgs/String 메시지를 사용하기 위한 과정으로 이는 자동으로 패키지 안의 String.msg 파일을 실행하게 된다. 자세한 정보는 링크를 참고 바란다.(wiki.ros.org/msg)
ros::init(argc, argv, "talker");
ROS를 초기화하는 과정으로 노드의 이름을 갖게되고 여기서 이름은 동작되는 시스템에서 유일하게 지정되어야한다.
ros::NodeHandle n;
해당 노드의 핸들러를 만든다. 처음 생성된 NodeHandle은 자동으로 노드를 초기화하고 마지막으로 제거될 때 해당 노드가 사용한 리소스를 정리(cleanup)한다.
Topic "chatter"에 대한 std_msgs/String 타입의 메시지를 publish할 것을 Master에게 알리게 된다.
Master는 chatter 을 subscribe하기 원하는 노드를 찾아 이 둘이 직접 연결되게 다.
두번째 argument는 publish queue의 크기를 지정하게 된다.
만약 publishing이 너무 빠르게 되면 이전의 메시지를 버리기 전에 지정된 버퍼 1000을 새롭게 채우는 문제가 발생할 수 있다.
위의 코드를 통해 chatter_pub이라는 이름의 ros::Publisher 객체를 생성하게 되고 advertise()는 해당 토픽으로 publish 가능한 객체인 ros::Publisher 클래스를 반환하며 그 객체의 publish()를 이용하여 원하는 메시지를 발행할 수 있다.
ros::Rate loop_rate(10);
ros::Rate 객체는 반복하고자하는 주기를 설정하게 된다. 위의 경우 10Hz로 반복하게되고 하단의 Rate::sleep()을 통해 지정된 시간을 지키기위해 sleep을 수행한다.
우리는 msg 파일을 통해 실행된 message-adapted class를 통해 ROS에서 메시지를 broadcasting 한다.
더 복잡한 형태의 데이터타입도 가능하지만 "data" 멤버를 갖는 표준적인 String 메시지를 사용하도록 하겠다.
chatter_pub.publishing(msg);
이제 연결된 노드에게 실질적은 broadcast를 실행한다.
ROS_INFO("%s", msg.data.c_str());
ROS_INFO는 출력을 담당한다.
ros::spinOnce();
본 예제에서는 callback을 받지 않으므로 필수적인 요소는 아니다. 이는 큐에 요청된 콜백함수를 처리하게 된다.
ROS는 여러개의 노드가 비동기 환경에서 작동하는 운영체제이다. 비동기 방식의 경우 어떤 작업을 실행시키고 결과와 상관없이 다음 작업을 수행하게 된다. 즉 A함수를 실행하고 A가 끝나든 말든 B를 시작하는 방식이다. 다만 요청했던 것의 할 일이 끝난 후 처리 결과를 콜백이라는 함수와 함께 알려준다.
2.Writing the Subscriber Node
2.1 The Code
src/listener.cpp 파일을 생성하고 아래의 내용을 붙여넣자.
#include "ros/ros.h"
#include "std_msgs/String.h"
/**
* This tutorial demonstrates simple receipt of messages over the ROS system.
*/
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line.
* For programmatic remappings you can use a different version of init() which takes
* remappings directly, but for most command-line programs, passing argc and argv is
* the easiest way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, argv, "listener");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
ros::NodeHandle n;
/**
* The subscribe() call is how you tell ROS that you want to receive messages
* on a given topic. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. Messages are passed to a callback function, here
* called chatterCallback. subscribe() returns a Subscriber object that you
* must hold on to until you want to unsubscribe. When all copies of the Subscriber
* object go out of scope, this callback will automatically be unsubscribed from
* this topic.
*
* The second parameter to the subscribe() function is the size of the message
* queue. If messages are arriving faster than they are being processed, this
* is the number of messages that will be buffered up before beginning to throw
* away the oldest ones.
*/
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
/**
* ros::spin() will enter a loop, pumping callbacks. With this version, all
* callbacks will be called from within this thread (the main one). ros::spin()
* will exit when Ctrl-C is pressed, or the node is shutdown by the master.
*/
ros::spin();
return 0;
}
다음과 같이 message_generation을 dependency에 추가하여(find_package를 찾아 수정하자) 메시지를 시작할 수 있다.
가끔씩 find_package의 모든 dependencies를 호출(call)하지 않아도 잘 빌드될때가 있는데 이는 catkin이 모든 프로젝트를 한번에 묶어 이전에 find_package를 호출한 적이 있다면 같은 값이 들어가기 때문이다. 하지만 이를 독립적으로 실행하게 되면 문제가 발생할 수 있다.
그리고 아래와 같이 catkin_package부분에 message runtime dependency를 추가하도록 하자.
다음으로 아래 부분을 수정하도록 하자.
.msg파일을 직접 추가해줌으로써 다른 .msg파일들이 추가될때 CMake가 확실히 이를 적용시킬 수 있도록 한다.
이제 generate_message() 함수가 확실히 호출될 수 있도록 하자.
이제 msg정의를 통해 source 파일을 실행시킬 준비가 되었다.
3. Using rosmsg
msg가 생성되어있다면 rosmsg 명령을 통해 이를 확인할 수 있다.
그럼 다음의 결과를 확인할 수 있다.
메시지 타입은 크게 2개의 파트로 분류된다.
* beginner_tutorials -- the package where the message is defined
* Num -- The name of the msg Num
혹시 어떤 msg가 있는 패키지가 생각나지 않는다면 다음의 코드를 통해 msg파일이 있는 패키지를 확인할 수 있다.
4. Using srv
4.1 Creating a srv
앞에서 사용한 패키지에 srv를 만들어보자.
새로운 srv 정의를 직접 작성하는 대신 다른 패키지에 존재하는 내용을 복사하여 사용하겠다.
다른 패키지에서 파일을 복사하여 사용하기 위해선 roscp 명령어를 사용할 수 있다.
그리고 아래와 같이 package.xml 파일을 수정한다(이 부분은 위에서 다룬 내용으로 이미 진행하였으면 넘어간다).
CMakeLists.txt파일도 아래와 같이 수정하는데 위에서 진행하였다면 넘어가도록 하자.
추가적으로 아래의 부분도 수정하도록 하자.
4.2 Using rossrv
rossrv show 명령어를 통해 해당 파일의 내용을 살펴볼 수 있다.
그럼 다음과 같은 결과를 확인할 수 있다.
그리고 rosmsg와 같이 패키지 이름 없이 service file을 찾을 수 있다.
5. Common step for msg and srv
CMakeLists.txt 파일을 아래와 같이 수정한다(이전에 완료했으면 하지않아도 된다).
이제 새로운 메시지를 생성하였으므로 패키지를 다시 make하자.
6. Getting help
대부분의 ROS 명령어는 -h라는 help 옵션이 존재하여 사용법을 잊었을 때 유용하게 사용할 수 있다.
rosed는 rosbash의 일부로 패키지 전체의 경로를 입력하지 않고 패키지의 이름을 통해 내부 파일을 직접적으로 설정할 수 있도록 한다.
예제를 통해 roscpp 패키지 안에 있는 Logger.msg 파일을 수정하는 과정을 나타낸다.
2. Using rosed with tab completion
이 방법을 통해 파일의 정확한 이름을 몰라도 패키지의 모든 파일을 쉽게 보고 수정할 수 있다.
3. Editor
rosed의 기본 에디터는 vim을 사용한다.
$ sudo apt-get update
$ sudo apt-get install vim
$ vi ~/.vimrc
위의 과정까지 진행하면 vi 편집기 창이 열리게 되는데 해당 창에서 i를 입력하면 쓰기 모드로 이동하게 된다.
편집기 창에서 쓰기 모드로 전환이 되면 아래의 설정들을 복사하여 해당 편집기 창에 붙여넣자.
(글 복사는 Ctrl+Insert, 붙여넣기는 Shift+Insert)
set number " line 표시
set ai " auto indent
set si " smart indent
set cindent " c style indent
set shiftwidth=4 " 자동 공백 채움 시 4칸
set tabstop=4 " tab을 4칸 공백으로
set ignorecase " 검색 시 대소문자 무시
set hlsearch " 검색 시 하이라이트
set nocompatible " 방향키로 이동 가능
set fileencodings=utf-8,euc-kr " 파일 저장 인코딩 : utf-8, euc-kr
set fencs=ucs-bom,utf-8,euc-kr " 한글 파일은 euc-kr, 유니코드는 유니코드
set bs=indent,eol,start " backspace 사용가능
set ruler " 상태 표시줄에 커서 위치 표시
set title " 제목 표시
set showmatch " 다른 코딩 프로그램처럼 매칭되는 괄호 보여줌
set wmnu " tab 을 눌렀을 때 자동완성 가능한 목록
syntax on " 문법 하이라이트 on
filetype indent on " 파일 종류에 따른 구문 강조
set mouse=a " 커서 이동을 마우스로 가능하도록
위의 코드에서 " 표시는 C언어의 // 와 같이 한 줄 주석을 의미한다.
그럼 이제 esc를 눌러 입력모드를 빠져나오고 ":"를 입력하여 마지막 라인으로 이동한다.
ROS에서 디버깅하고 많은 노드들을 동시에 시작하기 위한 rqt_console과 rqt_logger_level을 알아보자.
1. Prerequisites rqt and turtlesim package
이번 과정은 rqt와 turtlesim 패키지를 모두 사용하므로 둘 다 설치를 진행한다.
2. Using rqt_console and rqt_logger_level
rqt_console은 ROS의 rogging 프레임워크를 통해 노드들의 결과를 표시해준다.
rqt_logger_level 은 노드들이 실행되면서 동시에 verbosity level(DEBUG, WARN, INFO and ERROR)을 변경할 수 있도록 한다.
rqt_console의 turtlesim의 출력을 확인해보고 rqt_logger_level을 turtlesim으로 변경해보자.
turtlesim을 시작하기 전에 새로운 터미널창을 열고 rqt_console과 rqt_logger_level을 실행하자.
그럼 아래와 같은 창이 팝업되게 된다.
그럼 이제 아래 명령어를 통해 turtlesim_node를 실행시키자.
기본으로 설정된 logger level이 INFO이므로 turtlesim 이 publish하는 정보들을 확인할 수 있게 된다.
이제 logger level을 WARN으로 변경해보자.
그리고 아래 코드를 입력하여 보면
콘솔에 아래와 같은 Warn 메시지들이 출력되는 것을 확인할 수 있다.
2.1 Quick Note about logger levels
Logging levels는 다음의 순서로 우선순위가 정해지게 된다.
Fatal은 가장 높은 우선순위를 갖고 Debug는 가장 낮은 우선순위를 갖는다. logger level을 설정함으로써 priority level 이상의 메시지들을 받게 된다. 예를들어, logger level을 WARN으로 설정하면 모든 Warn, Error, Fatal logging messages를 받게 된다.
그럼 이제 turtlesim 노드를 종료하고 다수의 turtlesim 노드들을 실행하기 위해 roslaunch를 사용해보자.
2.2 Using roslaunch
roslaunch는 launch file에 정의된대로 노드들을 실행하게 된다.
우선 앞에서 생성하고 빌드했던 beginner_tutorials 패키지의 경로로 이동하자.
만약 위의 명령에서 "roscd:No such package/stack 'beginner_tutorials'" 라는 에러가 출력되면 setup파일의 설정을 source하여 저장해야한다.
위의 setup과정에서 실행한 turtlesim_node와 turtle_teleop_key 노드들은 ROS Topic을 통해 서로 통신하게 된다.
tuetle_teleop_key는 키보드의 입력을 publishing하게 되고 tuetlesim_node 노드는 같은 Topic을 subscribe하여 키의 입력을 전달받데 된다. 구조는 rqt_graph를 이용하여 쉽게 시각화시킬 수 있다.
2.1 Using rqt_graph
rqt_graph는 ROS 시스템의 구성을 그래프로 보여주는 도구로 rqt package의 한 부분이다.
아래 명령어를 통해 작업을 진행한다.
그럼 아래 명령어를 통해 Setup과정에서 진행한 ROS시스템의 구성을 살펴보자.
위의 그래프를 통해 /turtle1/command_velocity 라는 이름의 Topic에 대해 /teleop_turtle 노드와 tutlesim 노드가 메시지(데이터)를 통신하는 관계임을 확인할 수 있다.
2.2 Introducing rostopic
rostopic 툴은 ROS Topics에 대한 정보를 얻기 위한 도구이다.
2.3 Using rostopic echo
rostopic echo는 토픽에 대해 publish된 내용들을 표시해준다.
위와 같은 구조로 사용하게 되고 이를 예제 적용하여 보면
위의 명령어를 입력한 후 teleop_key 터미널창에 방향키 입력을 통해 거북이를 움직여보면 값이 변화하는 것을 확인할 수 있다.
이제 rqt_graph를 확인하여 보면 아래와 같이 붉은색으로 rostopic echo 노드가 생성되어 turtle1/command_velocity 토픽을
subscribe 하는것을 확인할 수 있다.
즉, turtle1/cmd_vel topic에 대해 teleop_turtle노드는 입력받는 키에 대한 정보를 publish하고 이를 나타내는 turtlesim 노드와 회전각 및 이동량을 전달받는 rostopic echo 노드가 topic을 subscribe하는 것을 확인할 수 있다.
2.4 Using rostopic list
rostopic list는 현재 topic에 대해 subscribe 및 publish 되는 노드들의 정보를 나타낸다.
rostopic list에 대한 argument들은 다음 명령어를 통해 확인할 수 있다.
3. ROS Messages
Topic들에 대한 통신(Communication)은 노드들 사이에 ROS 메시지를 전송함으로서 진행된다.
publisher(turtle_teleop_key)와 subscriber(turtlesim_node)가 통신하기 위해서는 각각 동일한 타입의 메시지를 송수신 하여야 한다.
즉, topic의 타입은 publish되는 메시지의 타입에 따라 정의되게 되고 이러한 토픽에 전송되는 메시지는 rostopic 타입을 통해 결정된다.
3.1 Using rostopic type
rostopic type는 publish되는 메시지 타입들을 보여준다.
우리의 예를 들면
위의 명령어를 통해 turtle1/cmd_vel 이라는 Topic의 메시지 타입을 확인하면 아래와 같은 결과를 확인할 수 있다.
그리고 메시지의 자세한 정보를 위해 rosmsg를 아래와 같이 사용하면 결과를 확인할 수 있다.
즉, geometry_msgs/Twist 라는 데이터 형은 직선방향, 각도에 대한 데이터형이고 이는 float64형임을 확인할 수 있다.
4. rostopic continued
4.1 Using rostopic pub
rostopic pub은 현재 토픽에서의 데이터를 publish한다.
위의 명령은 turtlesim 노드에게 직선속도 2.0과 각속도 1.8을 갖고 움직여라 라는 의미를 담고있다.
그럼 위와 같은 결과를 볼 수 있다.
굉장히 복잡한 명령어이므로 자세히 살펴보자.
우선 가장 앞에 등장하는 rostopic pub은 해당 topic에게 메시지를 전송하겠다는 것을 의미한다.
* -1(dash-one) 은 한번의 메시지만 publish한 후 종료하라는 의미를 담고있다.
* turtle1/cmd_vel 은 publish할 토픽의 이름을 나타낸다.
* geometry_msgs/Twist 는 topic을 publish할 때, 메시지의 타입을 나타낸다.
* --(double dash)는 해당 argument에 값을 넣어주지 않기 위한 과정이다.
* 앞에서와 같이 geometry/Twist 메시지는 각각 3개의 floating 요소들로 구성된다.
그리고 거북이를 계속 움직이기 위해선 아래 명령어와 같이 rostopic pub -r 명령어를 사용하자.