3주차 ROS 스터디로 ROS 기본 프로그래밍에 대한 강의를 수강하였다.
https://www.youtube.com/playlist?list=PLX-Ur4rl2-qwMK8H9FjTxjyKC-XeoP8Jg
2024 ROS 스터디 3주차
www.youtube.com
우선, 아래는 ROS프로그래밍을 위해 알아둬야 할 사항을 정리 해 둔 것이다. 좌표 표현 방식과 quantity에 대한 단위들을 정리 해 두었다.
Topic
단방향 메시지 통신 방식인 토픽의 실습을 다음과 같이 진행해보겠다.
$cd ~/catkin_ws/src
$ catkin_create_pkg ros_tutorials_topic message_generation std_msgs roscpp
명령어를 통해 패키지를 생성하고,
$ gedit package.xml 를 통해 아래와 같이 패키지 설정 파일을 수정하였다.
다음으로, gedit CMakeLists.txt 명령어를 통해 아래와 같이 빌드 설정 파일을 수정하였다.
다음으로, $ mkdir msg , $ cd msg , $ gedit MsgTutorial.msg 명령어를 통해 time stamp, int32 data의 메시지 파일을 작성하였다.
다음으로,$ cd src → ros_tutorials_topic, $ gedit topic_publisher.cpp 명령어를 통해 퍼블리셔 노드를 작성하고,
#include "ros/ros.h" // ROS 기본 헤더파일
#include "ros_tutorials_topic/MsgTutorial.h" // MsgTutorial 메시지 파일 헤더(빌드 후 자동 생성됨)
int main ( int argc , char ** argv ) // 노드 메인 함수
{
ros :: init ( argc , argv , "topic_publisher" ); // 노드명 초기화
ros ::NodeHandle nh;
// 퍼블리셔 선언, ros_tutorials_topic 패키지의 MsgTutorial 메시지 파일을 이용한
// 퍼블리셔 ros_tutorial_pub 를 작성한다. 토픽명은"ros_tutorial_msg" 이며,
// 퍼블리셔 큐(queue) 사이즈를 100개로 설정한다는 것이다
ros ::Publisher ros_tutorial_pub = nh . advertise < ros_tutorials_topic ::MsgTutorial > ( "ros_tutorial_msg" , 100 );
// 루프 주기를 설정한다. "10" 이라는 것은 10Hz를 말하는 것으로 0.1초 간격으로 반복된다
ros ::Rate loop_rate ( 10 );
// MsgTutorial 메시지 파일 형식으로 msg 라는 메시지를 선언
ros_tutorials_topic ::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 ;
}
$roscd ros_tutorials_topic/src, $ gedit topic_subscriber.cpp 명령어를 통해 서브스크라이버 노드를 작성하였다.
#include "ros/ros.h" // ROS 기본 헤더파일
#include "ros_tutorials_topic/MsgTutorial.h" // MsgTutorial 메시지 파일 헤더 (빌드 후 자동 생성됨)
// 메시지 콜백 함수로써, 밑에서 설정한 ros_tutorial_msg라는 이름의 토픽
// 메시지를 수신하였을 때 동작하는 함수이다
// 입력 메시지로는 ros_tutorials_topic 패키지의 MsgTutorial 메시지를 받도록 되어있다
void msgCallback ( const ros_tutorials_topic :: MsgTutorial :: ConstPtr & msg )
{
ROS_INFO ( "recieve msg = %d " , msg -> stamp . sec ); // stamp.sec 메시지를 표시한다
ROS_INFO ( "recieve msg = %d " , msg -> stamp . nsec ); // stamp.nsec 메시지를 표시한다
ROS_INFO ( "recieve msg = %d " , msg -> data ); // data 메시지를 표시한다
}
int main ( int argc , char ** argv ) // 노드 메인 함수
{
ros :: init ( argc , argv , "topic_subscriber" ); // 노드명 초기화
ros ::NodeHandle nh; // ROS 시스템과 통신을 위한 노드 핸들 선언
// 서브스크라이버 선언, ros_tutorials_topic 패키지의 MsgTutorial 메시지 파일을 이용한
// 서브스크라이버 ros_tutorial_sub 를 작성한다. 토픽명은"ros_tutorial_msg" 이며,
// 서브스크라이버 큐(queue) 사이즈를 100개로 설정한다는 것이다
ros ::Subscriber ros_tutorial_sub = nh . subscribe ( "ros_tutorial_msg" , 100 , msgCallback);
// 콜백함수 호출을 위한 함수로써, 메시지가 수신되기를 대기,
// 수신되었을 경우 콜백함수를 실행한다
ros :: spin ();
return 0 ;
}
이후, $ rosrun ros_tutorials_topic topic_publisher 를 통해 퍼블리셔를 실행하고, $ rosrun ros_tutorials_topic topic_subscriber 명령어를 통해 서브스크라이버를 실행하였다. $rqt_graph 를 통해 실행된 노드들의 통신상태를 살펴보면 아래와 같다.
Service
양방향 일회성 통신 방식인 서비스의 실습을 다음과 같이 진행해보겠다.
$ cd ~/catkin_ws/src
$ catkin_create_pkg ros_tutorials_service message_generation std_msgs roscpp 명령어를 통해 패키지를 생성하고,
$ gedit package.xml 명령어를 통해 다음과 같이 패키지 설정파일을 수정하였다.
<? xml version = "1.0" ?>
< package >
< name > ros_tutorials_service </ name >
< version > 0.1.0 </ version >
< description > ROS turtorial package to learn the service </ description >
< license > Apache License 2.0 </ license >
< author email = "pyo@robotis.com" > Yoonseok Pyo </ author >
< maintainer email = "pyo@robotis.com" > Yoonseok Pyo </ maintainer >
< url type = "bugtracker" > https : //github.com/ROBOTIS-GIT/ros_tutorials/issues</url>
< url type = "repository" > https : //github.com/ROBOTIS-GIT/ros_tutorials.git</url>
< url type = "website" > http: //www.robotis.com</url>
< buildtool_depend > catkin </ buildtool_depend >
< build_depend > roscpp </ build_depend >
< build_depend > std_msgs </ build_depend >
< build_depend > message_generation </ build_depend >
< run_depend > roscpp </ run_depend >
< run_depend > std_msgs </ run_depend >
< run_depend > message_runtime </ run_depend >
< export ></ export >
</ package >
다음으로 $ gedit CMakeLists.txt 명령어를 통해 다음과 같이 빌드 설정 파일을 수정하였다.
cmake_minimum_required (VERSION 2.8.3 )
project (ros_tutorials_service)
## 캐킨 빌드를 할 때 요구되는 구성요소 패키지이다.
## 의존성 패키지로 message_generation, std_msgs, roscpp이며 이 패키지들이 존재하지 않으면 빌드 도중에 에러가 난다.
find_package (catkin REQUIRED COMPONENTS message_generation std_msgs roscpp)
## 서비스 선언: SrvTutorial . srv
add_service_files (FILES SrvTutorial . srv )
## 의존하는 메시지를 설정하는 옵션이다.
## std_msgs가 설치되어 있지 않다면 빌드 도중에 에러가 난다.
generate_messages (DEPENDENCIES std_msgs)
## 캐킨 패키지 옵션으로 라이브러리, 캐킨 빌드 의존성, 시스템 의존 패키지를 기술한다.
catkin_package (
LIBRARIES ros_tutorials_service
CATKIN_DEPENDS std_msgs roscpp
)
## 인클루드 디렉터리를 설정한다.
include_directories (${catkin_INCLUDE_DIRS})
## service_server 노드에 대한 빌드 옵션이다.
## 실행 파일, 타겟 링크 라이브러리, 추가 의존성 등을 설정한다.
add_executable (service_server src / service_server . cpp )
add_dependencies (service_server ${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS})
target_link_libraries (service_server ${catkin_LIBRARIES})
## service_client 노드에 대한 빌드 옵션이다.
add_executable (service_client src / service_client . cpp )
add_dependencies (service_client ${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS})
target_link_libraries (service_client ${catkin_LIBRARIES})
이후 서비스 파일을 작성하였다. $ mkdir srv 명령어로 패키지에 srv라는 서비스 폴더를 새로 만들고,
$ gedit SrvTutorial.srv 명령어를 통해 서비스 파일에 int64 a, int64 b, int64 result를 작성하였다.
다음으로, $ gedit service_server.cpp 명령어를 통해 다음과 같이 서비스서버 노드를 작성하였다.
#include "ros/ros.h" // ROS 기본 헤더 파일
#include "ros_tutorials_service/SrvTutorial.h" // SrvTutorial 서비스 파일 헤더 (빌드후 자동 생성됨)
// 서비스 요청이 있을 경우, 아래의 처리를 수행한다
// 서비스 요청은 req, 서비스 응답은 res로 설정하였다
bool calculation ( ros_tutorials_service :: SrvTutorial :: Request & req ,
ros_tutorials_service :: SrvTutorial :: Response & res )
{
// 서비스 요청시 받은 a와 b 값을 더하여 서비스 응답 값에 저장한다
res . result = req . a + req . b ;
// 서비스 요청에 사용된 a, b 값의 표시 및 서비스 응답에 해당되는 result 값을 출력한다
ROS_INFO ( "request: x= %ld , y= %ld " , ( long int ) req . a , ( long int ) req . b );
ROS_INFO ( "sending back response: %ld " , ( long int ) res . result );
return true ;
}
int main ( int argc , char ** argv ) // 노드 메인 함수
{
ros :: init (argc, argv, "service_server" ); // 노드명 초기화
ros ::NodeHandle nh; // 노드 핸들 선언
// 서비스 서버 선언, ros_tutorials_service 패키지의 SrvTutorial 서비스 파일을 이용한
// 서비스 서버 ros_tutorials_service_server를 선언한다
// 서비스명은 ros_tutorial_srv이며 서비스 요청이 있을 때,
// calculation라는 함수를 실행하라는 설정이다
ros ::ServiceServer ros_tutorials_service_server = nh . advertiseService ( "ros_tutorial_srv" , calculation);
ROS_INFO ( "ready srv server!" );
ros :: spin (); // 서비스 요청을 대기한다
return 0 ;
}
다음으로 $ gedit service_client.cpp명령어를 통해 다음과 같이 서비스 클라이언트 노드를 작성하였다.
#include "ros/ros.h" // ROS 기본 헤더 파일
#include "ros_tutorials_service/SrvTutorial.h" // SrvTutorial 서비스 파일 헤더 (빌드후 자동 생성됨)
#include <cstdlib>
int main ( int argc , char ** argv ) // 노드 메인 함수
{
ros :: init ( argc , argv , "service_client" ); // 노드명 초기화
if ( argc != 3 ) // 입력값 오류 처리
{
ROS_INFO ( "cmd : rosrun ros_tutorials_service service_client arg0 arg1" );
ROS_INFO ( "arg0: double number, arg1: double number" );
return 1 ;
}
ros ::NodeHandle nh; // ROS 시스템과 통신을 위한 노드 핸들 선언
// 서비스 클라이언트 선언, ros_tutorials_service 패키지의 SrvTutorial 서비스 파일을 이용한
// 서비스 클라이언트 ros_tutorials_service_client를 선언한다
// 서비스명은"ros_tutorial_srv"이다
ros ::ServiceClient ros_tutorials_service_client =
nh . serviceClient < ros_tutorials_service ::SrvTutorial > ( "ros_tutorial_srv" );
// srv라는 이름으로 SrvTutorial 서비스 파일을 이용하는 서비스를 선언한다
ros_tutorials_service ::SrvTutorial srv;
// 서비스 요청 값으로 노드가 실행될 때 입력으로 사용된 매개변수를 각각의 a, b에 저장한다
srv . request . a = atoll ( argv [ 1 ]);
srv . request . b = atoll ( argv [ 2 ]);
// 서비스를 요청하고, 요청이 받아들여졌을 경우, 응답 값을 표시한다
if ( ros_tutorials_service_client . call (srv))
{
ROS_INFO ( "send srv, srv.Request.a and b: %ld , %ld " , ( long int ) srv . request . a , ( long int ) srv . request . b );
ROS_INFO ( "receive srv, srv.Response.result: %ld " , ( long int ) srv . response . result );
}
else
{
ROS_ERROR ( "Failed to call service ros_tutorial_srv" );
return 1 ;
}
return 0 ;
}
이제 $ rosrun ros_tutorials_service service_server 명령어를 통해 서비스 서버를 실행 시키고,
$ rosrun ros_tutorials_service service_client 2 3 명령어를 통해 서비스 클라이언트를 실행시키면 아래와 같은 화면을 확인 할 수 있다.
추가로 GUI도구인 service caller를 사용하면 아래와 같다.
Parameter
위에서 다루었던 서비스 서버와 클라이언트 노드에서 서비스 요청으로 입력된 a와 b를 단순히 덧셈하는 것이 아니라, 파라미터를 활용하여 사칙연산을 수행하도록 할 것이다.
이를 위해 우선, 위에서 수정하였던 service_server.cpp소스를 다음과 같이 수정하였다.
#include "ros/ros.h" // ROS 기본 헤더파일
#include "ros_tutorials_service/SrvTutorial.h" // SrvTutorial 서비스 파일 헤더 (빌드 후 자동 생성됨)
#define PLUS 1 // 덧셈
#define MINUS 2 // 빼기
#define MULTIPLICATION 3 // 곱하기
#define DIVISION 4 // 나누기
int g_operator = PLUS ;
// 서비스 요청이 있을 경우, 아래의 처리를 수행한다
// 서비스 요청은 req, 서비스 응답은 res로 설정하였다
bool calculation ( ros_tutorials_service :: SrvTutorial :: Request & req ,
ros_tutorials_service :: SrvTutorial :: Response & res )
{
// 서비스 요청시 받은 a와 b 값을 파라미터 값에 따라 연산자를 달리한다.
// 계산한 후 서비스 응답 값에 저장한다
switch (g_operator)
{
case PLUS :
res . result = req . a + req . b ; break ;
case MINUS :
res . result = req . a - req . b ; break ;
case MULTIPLICATION :
res . result = req . a * req . b ; break ;
case DIVISION :
if ( req . b == 0 ){
res . result = 0 ; break ;
}
else {
res . result = req . a / req . b ; break ;
}
default :
res . result = req . a + req . b ; break ;
}
// 서비스 요청에 사용된 a, b값의 표시 및 서비스 응답에 해당되는 result 값을 출력한다
ROS_INFO ( "request: x= %ld , y= %ld " , ( long int ) req . a , ( long int ) req . b );
ROS_INFO ( "sending back response: [ %ld ]" , ( long int ) res . result );
return true ;
}
int main ( int argc , char ** argv ) // 노드 메인 함수
{
ros :: init (argc, argv, "service_server" ); // 노드명 초기화
ros ::NodeHandle nh; // ROS 시스템과 통신을 위한 노드 핸들 선언
nh . setParam ( "calculation_method" , PLUS ); // 매개변수 초기설정
// 서비스 서버 선언, ros_tutorials_service 패키지의 SrvTutorial 서비스 파일을 이용한
// 서비스 서버 service_server를 작성한다. 서비스명은"ros_tutorial_srv"이며,
// 서비스 요청이 있을 때, calculation라는 함수를 실행하라는 설정이다.
ros ::ServiceServer ros_tutorial_service_server = nh . advertiseService ( "ros_tutorial_srv" , calculation);
ROS_INFO ( "ready srv server!" );
ros ::Rate r ( 10 ); // 10 hz
while ( 1 )
{
nh . getParam ( "calculation_method" , g_operator); // 연산자를 매개변수로부터 받은 값으로 변경한다
ros :: spinOnce (); // 콜백함수 처리루틴
r . sleep (); // 루틴 반복을 위한 sleep 처리
}
return 0 ;
}
이후 $ rosservice call /ros_tutorial_srv 명령어로 a,b에 들어갈 변수를 입력하고,
$ rosparam set /calculation_method 명령어를 통해 사칙연산 방식을 수정할 수 있다. 결과는 다음과 같다.
roslaunch
하나의 노드를 실행하는 rosrun과 달리, roslaunch는 하나 이상의 정해진 노드를 실행하는 명령어이다.