Ch7. ROS 기본 프로그래밍
- ROS Programming 전 알아둬야 할 사항
- 표준 단위 : 주고 받는 Message들의 단위 통일 (SI Unit 사용)
- 좌표 표현 방식 : 정회전은 반시계 방향 (오른손 법칙). https://www.ros.org/reps/rep-0103.html#coordinate-frame-conventions 에서 각 version에 맞는 rep 찾아서 규칙 찾아보기
- Rep (ROS Enhancement Proposal) : ROS community를 위한 design 문서 정보를 제공하거나 ROS의 새로운 feature, process, environment를 설명
- 프로그래밍 규칙 : wiki.org에 명시되어 있음 (http://wiki.ros.org/CppStyleGuide)
- Package, Topic, Service, File, Library, Variable, Namespace는 under score('_') 사용
- Ex) std_msgs, robot_node
- Source file은 .cpp 확장자 사용
- Header file은 .h 확장자 사용
- 모든 header들은 다수의 inclusion에 대해 #ifndef guard로 보호받아야 하며, 아래와 같은 guard는 License Statement 바로 뒤, 그리고 모든 code들 전에 실행되어야 하고 file의 끝부분에서 끝나야 함
# ifndef PACKAGE_PATH_FILE_H
# define PACKAGE_PATH_FILE_H
...
# endif
- Class, Type, Function, Method는 CamelCased (띄어쓰기 없이 대문자로 구분) 사용. 단 각자의 argument는 '_' 사용
- Ex) class ExampleClass, void CalculateVelocity
- Constant는 전부 대문자 사용
- Global Variable은 거의 사용하지 않으며, 사용시 variable name 앞에 'g_'를 붙임
- Editor 사용시 indent는 spacebar 두번. Tab키 사용 X
- Line의 최대 길이는 120글자
- Code는 반드시 documented 되어야 함. Doxygen을 통해 코드를 자동으로 document 함
- Doxygen : Documentation 생성기로, Software 참조 설명문을 작성하는데 사용하는 도구. 설명문을 code 내에 작성하여 상대적으로 최신화 상태를 유지하기 용이함
- ROS에서는 printf, cout 대신 rosconsole을 통해 출력을 수행. rosconsole의 출력은 /rosout에 publish 되어 network상의 모든 이용자가 볼 수 있음
- rosout : 항상 기동하는 기본적인 node로, 각 node들의 debugging 정보를 가지고 있다
- Subscribed topic
- /rosout (rosgraph_msgs/Log) : Logging message를 publish하는 standard ROS topic
- Published topic
- /rosout_agg (rosgraph_msgs/Log) : /rosout에 publish된 messgae의 집계된 feed
- Macro는 가능한 사용 X. Inline Function이나 const variable과 달리 typed, scoped 되지 않음
- Message 통신의 종류 (복습)
- Topic : 연속성을 가지는 단방향 통신
- Service : 1회성을 가지는 양방향 통신
- Action : 기존의 서비스의 기능에서 피드백 기능 추가
- roscore 실행 시의 3가지 기능
- ROS Master 기능을 함 (Node 정보 관리)
- rosout : 각종 log 기록 전담
- Parameter server 구동.
- Parameter Server는 사용자가 정해놓은 각종 parameter들을 기록함. 임의의 node나 직접 명령을 통해 parameter 값을 바꿀 수 있다
- Topic, Service 직접 구현
1) Make Package
$ cd~/catkin_ws/src
- catkin_ws/src : 다양한 package들이 들어가는 곳
- ~ : Home Directory를 의미
$ catkin_create_pkg ros_tutorials_topic message_generation std_msgs roscpp
- 'catkin_create_pkg' 명령어를 이용하여 'ros_tutorials_topic' 라는 이름의 package를 생성함
- 'message_generation', 'std_msgs', 'roscpp' 는 package가 이 3개의 package에 의존성이 있음을 의미
- src는 source code가 들어가는 곳
- 처음에 src 폴더는 비어있음
- 위 두 폴더와 파일들은 package를 작성할 때 가장 기본적으로 구성되어야 함
- include : Header File Folder
- src : Source Code Folder
- CMakeLists.txt : Build Setting File. ROS의 build system인 catkin은 기본적으로 CMake를 이용하므로 이 file에 build 환경을 기술한다
- package.xml : Package Setting File. Package 이름, author, license, dependency등을 기술한다
$ cd ros_tutorials_topic
$ ls
- ls(list) 명령어를 통해 해당 directory에 있는 구성요소들을 볼 수 있음
2) Package 설정 File (package.xml) 수정
$ gedit package.xml
- 파일을 연 후 아래의 내용들을 위 파일에 붙여넣음
- ROS의 필수 설정 file 중 하나인 package.xml은 package 정보를 담은 xml 파일로써 package name, author, license, dependency packages 등을 기술함
<?xml version="1.0"?>
// xml의 버전을 의미. 1.0이상 사용 가능함을 의미. 필수적이진 않으나 Encoding을 위해 넣는 것이 좋음
<package format="2">
// package 태그 시작점. </package>까지가 ROS package 설정 부분
<name>ros_tutorials_topic</name>
// package의 이름. Package 생성시 입력한 이름이 사용됨
<version>0.1.0</version>
// 버전 태그. 개인적으로 쓸 땐 큰 의미 없으나 공식 package를 만들 땐
// 매우 중요. 뭔가 수정해서 다시 올리려면 버전을 update해서 올려야 함
<description>ROS turtorial package to learn the topic</description>
// package에 대한 대략적인 설명. 공식 package를 만들 때 해당 package에 대한 wiki 페이지가
// 자동적으로 만들어지게 할 수 있음. wiki페이지에서 가장 위에 있는 설명란에 해당
<license>Apache 2.0</license>
// Package 개발 시 적용된 license
<author email="pyo@robotis.com">Yoonseok Pyo</author>
// 저자. 여려명일 시 여러 줄 작성
<maintainer email="pyo@robotis.com">Yoonseok Pyo</maintainer>
// maintanier와 author는 보통 초반에는 동일 인물이나 후에 변경 가능
// maintanier는 해당 package를 관리하는 사람
<url type="website">http://www.robotis.com</url>
// Package를 설명하는 website
<url type="repository">https://github.com/ROBOTIS-GIT/ros_tutorials.git</url>
// 이 package의 source code는 해당 github에 들어가면 찾아볼 수 있다
<url type="bugtracker">https://github.com/ROBOTIS-GIT/ros_tutorials/issues</url>
// package에서 issue가 있을 시 해당 url에 올려달라는 의미
<buildtool_depend>catkin</buildtool_depend>
// build할 때 어떤걸 쓸 것인가
<depend>roscpp</depend> // cpp 관련 API를 불러오기 위함
<depend>std_msgs</depend> // 어떤 message를 쓸지 표시
<depend>message_generation</depend> // message를 만들기 위해 해당 package 사용
// Package의 의존성들
<export></export >
// ROS에서 명시하지 않은 tag명을 사용할 때 쓰임. 일반적인 경우 사용하지 않음
</package> // package 태그 종료점
<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_generation</run_depend>
// 위의 긴 명령어들은 Package Format V2. 지금 명령어들은 V1
// build_depend는 build할 때 의존성, run_depend는 실행할 때의 의존성. 일반적으로는 둘이 동일
- xml file의 각 tag과 attribute
- <launch>, </launch> : Launch file의 시작과 끝
- <node> : Node 실행
- 필수 arrtibute
- pkg : Package 이름
- type : 실행할 node나 실행 file의 이름
- name : Node의 실행 이름. 일반적으로는 type과 동일. Node 내부에서 설정한 이름보다 우선적으로 사용
- Option attribute
- args : Node에 parameter를 전달
- output : stdout/stderr 표시 방법 설정. 기본은 log로, $ROS_HOME/log에 작성. screen으로 설정 시 terminal에 출력
- respawn : true일 경우 Node 종료시 자동으로 재시작
- machine : 지정한 기기에서 node 실행
- <machine> : Node를 실행시킬 기기를 지정정. 모든 node가 local network에서 구동되는 경우는 사용하지 않음
- 필수 arrtibute
- name : 기기의 이름 지정 <node> tag에서 사용하는 'machine' attribute와 대응됨
- address : 기기의 network 주소 지정
- env-loader : 원격 기기의 환경 파일 지정정
- Option attribute
- default = "true or false or never" : 해당 기기가 모든 node들의 기본 실행 기기가 될 것인지 설정
- timeout : roslaunch에서 몇 초간 해당 기기에 대한 응답이 없으면 연결 실패로 간주할 것인지 시간을 지정
- <include> : Launch file 내에서 다른 launch xml file을 가져옴
- 필수 arrtibute
- file = "$(find package_name)/path/file_name.xml" : Include할 file 이름 입력
- Option attribute
- ns : File을 가져올 namespace 입력
- <remap> : Node 이름, topic 이름 등의 node에서 사용 중인 ROS 변수의 이름을 변경
- from : remap할 원래 이름
- to : 바꿀 이름
- <env> : 이미 실행된 Node의 환경 변수 설정 (Launch file의 환경 변수는 수정 불가능하며 거의 쓰이지 않음 )
- name : 설정할 환경 변수의 이름
- value : 해당 환경 변수의 값 설정
- <param> : Parameter 값 설정 (Parameter Server로 저장되어 여러 곳에서 참조 가능)함. 단일 parameter를 처리
- 필수 arrtibute
- name : Parameter 이름 설정
- Option attribute
- value : Parameter 값 설정
- type : Parameter 자료형 지정(std/int/double/bool). 미지정시 roslaunch의 규칙에 따라 지정
- <rosparam> : ROS parameter 설정 (YAML file을 이용해 parameter server의 parameter 값을 확인하고 수정). Parameter가 많은 경우 이 방식이 편함
- 필수 arrtibute
- file = "$(find pkg_name)/path/file_name.xml : rosparam file의 이름 입력. rosparam command로 load나 dump로 설정한 경우에만 사용
- param = "param_name" : Parameter 이름 지정
- Option attribute
- command = "load or dump or delete" : rosparam에서 사용할 command 지정. 기본값은 load (load는 추가 command로 간주하며 paramater의 directory 또는 namespace를 선언하면 namespace에 선언된 다른 parameter에 추가됨)
- ns = "namespace" : Parameter를 지정된 namespace에 scope 시킴
- subst_value = true or false : YAML file 내에 있는 'substitution args'의 사용 여부 설정
- <group> : 다수의 node를 group으로 묶음. ns = "namespace"를 통해 namespace 지정 가능
- ns="namespace" : Node group을 지정된 namespace에 scope
- clear_params = "true or false" : Launch 실행 전 group의 namespace parameter를 전부 제거. true인 경우 반드시 ns attribute와 같이 사용해야 함
- <test> : 노드를 테스트할 때 사용.<node>와 비슷하지만 테스트에 사용할 수 있는 옵션들이 추가되어 있으며 respwan, output, machine attribute는 사용 불가
- <arg> : Launch file 내부에서 지역변수처럼 사용될 값을 정의. 자주 사용
- 필수 arrtibute
- name : 이름 지정
- Option attribute
- default : 기본값 설정. 아래의 'value' attribute와 동시 사용 불가능
- value : 값 지정 'default' attribute와 동시 사용 불가능
3) Build Setting File(CMakeLists.txt) 수정
- CMakeLists.txt (Build Setting File)에 아래 코드 복사
- CMakeLists는 source code 작성 후 execution file을 만들 때와 실제로 우리가 작성한 node를 build할 때 사용되는 option들에 대한 정보를 가진다
cmake_minimum_required(VERSION 2.8.3)
## OS에 설치된 cmake의 최소 요구 버전으로, 최신 버전 필요. 위 버전보다 높아도 상관없음
project(ros_tutorials_topic)
## 괄호 안의 이름은 package.xml에서 입력한 package의 이름과 같아야 함
## 다르면 build가 되지 않음
find_package(catkin REQUIRED COMPONENTS
message_generation
std_msgs
roscpp
)
## catkin build시 요구되는 구성요소 package.
## 의존성 패키지로 message_generation, std_msgs, roscpp이 있으며 이 패키지들이 존재하지 않으면 빌드 도중에 에러 발생.
## 사용자가 만든 package가 dependency package를 먼저 설치하게 만드는 option
catkin_python_setup()
## Python 사용시 설정하는 option으로, python 설치 process인 setup.py를 부르는 역할을 한다
add_message_files(
FILES
MsgTutorial.msg
)
## 메시지 선언: MsgTutorial.msg (아래에서 새로 만들 message)
## 사용하는 message file을 추가하는 option
## FILES 사용시 package folder의 msg folder 안의 .msg file들을 참조함
add_service_files(
FILES
Service.srv
)
## 사용하는 service file을 추가하는 option
## FILES 사용시 package folder의 srv folder 안의 .srv file들을 참조함
generate_messages(
DEPENDENCIES
std_msgs
)
## 의존 message 설정 option. std_msgs가 설치되어 있지 않다면 빌드 도중에 에러가 난다.
## std_msgs build시 MsgTutorial.msg에 포함됨을 의미 : dependency가 존재함을 의미
## DEPENDENCIES option에 의해 std_msgs라는 message package를 사용하겠다는 설정
catkin_package(
INCLUDE_DIRS include
LIBRARIES ros_tutorials_topic ## generaly use package`s name
CATKIN_DEPENDS std_msgs roscpp
DEPENDS system_lib
)
## catkin build option으로 library, catkin build dependency, system 의존 package를 기술한다.
## INCLUDE_DIRS는 뒤에 설정한 package 내의 folder인 include의 header file을 사용하겠다는 설정
## LIBARIES는 뒤에 설정한 package의 library를 사용하겠다는 설정
## CATKIN_DEPENDS 뒤에는 roscpp나 std_msg 등 의존 package 지정
## DEPENDS는 system 의존 package를 기술
include_directories(
${catkin_INCLUDE_DIRS}
)
## Include directory 지정 option
## {catkin_INCLUDE_DIRS}는 각 package 내의 include folder를 의미하고 이 안의 header file을 사용하겠다는 설정
## (${catkin_INCLUDE_DIRS}, include) 작성시 package의 include folder를 사용할 것을 의미
add_library(my_first_ros_pkg
src/${PROJECT_NAME}/my_first_ros.cpp
)
## Build후 생성할 library 지정. src/${PROJECT_NAME}/my_first_ros.cpp을 참조하여
## my_first_ros_pkg라는 library를 생성한다
## !!아래 명령어들은 주로 사용되는 명령어로, 매우 중요함!!
## topic_publisher node에 대한 build option
## -> 이 tutorial에선 topic을 보내는 publisher node와 topic을 받는 subscriber node를 만듦
## 따라서 2개의 execution file을 생성
add_executable(topic_publisher src/topic_publisher.cpp)
## Build 후 생성할 execution file을 지정
## src/topic_publisher.cpp file을 참조하여 topic_publisher라는 execution file을 생성
## execute file, target link library, 추가 dependency 등을 설정할 수 있다
add_dependencies(topic_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS})
## Package build에 앞서 생성해야 할 message header file이 있으면 build 전에 우선적으로 message를
## 생성하게 하는 option
## 위의 경우 topic_publisher_messgaes_cpp를 우선 build하고 topic_publisher를 build함
## topic_publisher은 node의 이름. 해당 node 생성시 src/topic_publisher.cpp 참고
target_link_libraries(topic_publisher
${catkin_LIBRARIES}
)
## topic_publisher node 생성 전 link해야 하는 library와 execution file을 link해주는 option
## dependencies를 가지는 build options를 conduct 할 수 있음
## topic_subscriber node에 대한 build option
## 위와 거의 동일
add_executable(topic_subscriber src/topic_subscriber.cpp)
add_dependencies(topic_subscriber ${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS})
target_link_libraries(topic_subscriber ${catkin_LIBRARIES})
4) Message File 작성
$ roscd ros_tutorials_topic # roscd 사용시 directory 대신 package의 이름을 사용
- ros_tutorials_topic package folder로 이동
$ add_message_files(FILES MsgTutorial.msg)
- 위의 CMakeLists.txt에서 설정한 내용 (node에서 사용할 message인 MSGTutorial.msg를 build시에 포함)
$ mkdir msg
$ cd msg
$ gedit MsgTutorial.msg
$ cd ..
- ros_tutorials_topic package에 msg라는 message folder를 생성 후 해당 folder로 이동
- gedit을 통해 MsgTutorial.msg file 작성
time stamp # stamp는 'time' type의 message
int32 data # data는 int32 type의 message
- MsgTutorial.msg에 위의 두 message 입력
- time, int32 는 message의 type
- time type은 message를 보내며 시간을 나타냄
- int32 type은 자료형
- 일반적으로 message file인 msg와 service file인 srv는 실행 node에 포함시키기 보다는 message file만으로 구성된 독립된 package로 만드는 것이 좋다
- Subscriber / Publisher node가 다른 computer에서 실행된다고 가정했을 때, 두 node간의 상호 의존성을 갖추기 위해 불필요한 node를 설치해야 함
- 하지만 message만의 package를 따로 만들어주면 그 독립적인 message package만 dependency option에 추가하면 되므로 불필요한 package간의 dependency를 없앨 수 있음
- 다만 위 예제에서는 코드를 간결하게 만들기 위해 message file을 실행 node에 포함함
5) Publisher Node 작성
add_executable(topic_publisher src/topic_publisher.cpp)
- 위의 CMakeLists.txt에서 설정한 내용 (src 폴더의 topic_publisher.cpp라는 파일을 build하여 topic_publisher라는 실행 file을 만듦)
$ roscd ros_tutorials_topic/src
$ gedit topic_publisher.cpp
- ros_tutorials_topic 패키지의 소스 폴더인 src 폴더로 이동 후 소스 파일 신규 작성 및 내용 수정
- cpp 파일에 아래 코드 복사
#include "ros/ros.h" // ROS 기본 헤더파일
#include "ros_tutorials_topic/MsgTutorial.h"// MsgTutorial 메시지 파일 헤더(빌드 후 자동 생성됨)
// write to use 'MsgTutorial' message
int main(int argc, char **argv) // 노드 메인 함수
{
ros::init(argc, argv, "topic_publisher"); // 노드명 초기화 (처음에 반드시 진행되어야 함)
ros::NodeHandle nh; // ROS 시스템과 통신을 위한 노드 핸들 선언
// Publisher 선언. ros_tutorials_topic 패키지의 MsgTutorial 메시지 파일을 이용한
// publisher ros_tutorial_pub를 작성한다.
// Topic명은 "ros_tutorial_msg" 이며, 퍼블리셔 큐(queue) 사이즈를 100개로 설정한다.
// Publish되는 messgae의 형태를 설정한다. option에서 가장 중요한 과정이다
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 메시지에 담는다(time type)
// stamp는 msg의 member 변수
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 메시지를 표시한다(nano second)
ROS_INFO("send msg = %d", msg.data); // data 메시지를 표시한다
// ROS_INFO는 printf와 동일한 기능을 한다 (ROS에서는 printf를 사용하지 않는다)
// 무엇을 출력할지에 따라 대신 사용할 수 있는 다수의 명령어가 있다
ros_tutorial_pub.publish(msg); // 메시지(msg)를 발행한다
loop_rate.sleep(); // 위에서 정한 루프 주기에 따라 슬립에 들어간다(10Hz)
++count; // count 변수 1씩 증가
}
return 0;
}
6) Subscriber Node
add_executable(topic_subscriber src/topic_subscriber.cpp)
- 위의 CMakeLists.txt에서 설정한 내용 (src 폴더의 topic_subscriber.cpp라는 파일을 build하여 topic_subscriber라는 실행 file을 만듦)
$ roscd ros_tutorials_topic/src
$ gedit topic_subscriber.cpp
- 패키지의 소스 폴더인 src 폴더로 이동 후 소스 파일 신규 작성 및 내용 수정
- cpp 파일에 아래 코드 복사 (Publisher node의 code와 거의 유사)
#include "ros/ros.h"
#include "ros_tutorials_topic/MsgTutorial.h"
// 메시지 콜백 함수로써, 밑에서 설정한 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 메시지를 표시한다
// write what will do with data received here
}
int main(int argc, char **argv) // 노드 메인 함수
{
ros::init(argc, argv, "topic_subscriber"); // 노드명 초기화
ros::NodeHandle nh; // ROS 시스템과 통신을 위한 노드 핸들 선언
// Subscriber 선언, ros_tutorials_topic 패키지의 MsgTutorial 메시지 파일을 이용한
// Subscriber ros_tutorial_sub 를 작성한다. 토픽명은 "ros_tutorial_msg" 이며,
// Subscriber 큐(queue) 사이즈를 100개로 설정한다
// Topic의 이름과 queue size는 publisher node와 반드시 같아야 한다
ros::Subscriber ros_tutorial_sub = nh.subscribe("ros_tutorial_msg", 100, msgCallback);
// 콜백함수 호출을 위한 함수로써, 메시지가 수신되기를 대기. Message 수신 전까지는 아무것도 하지 않는다
// 수신되었을 경우 콜백함수(msgCallback)를 실행한다
ros::spin();
return 0;
}
- 하나의 node는 subscriber, publisher, service등의 여러 역할을 수행할 수 있다
ros::Publisher publisher_name = node_handle_name.advertise<package_name::message_file>("topic_name", queue_size);
ros::Subscriber subscriber_name = node_handle_name.subscribe("topic_name", queue_size, CallbackFunction_name);
- Publisher / Subscriber node 선언 statement
- 둘의 topic name과 queue size는 반드시 같아야 한다
7) Build ROS Node
- File System 관련 내용
- Package의 source code file은 package 파일의 src, message file은 msg에 있다
- ~/catkin_ws/src/ros_tutorials_topic/src, ~/catkin_ws/src/ros_tutorials_topic/msg
- 이를 기반으로 build된 결과물은 catkin_ws의 build, devel 폴더에 각각 저장됨
- build 폴더에는 catkin build시 사용된 setting 내용이 저장
- /devel/lib/ros_tutorials_topic 폴더에는 execute file이 저장
- /devel/include/ros_tutorials_topic 폴더에는 message 파일로부터 자동 생성된 message header file이 저장
$ cd ~/catkin_ws
$ catkin_make
- catkin 폴더로 이동 후 catkin 빌드 실행 (Publisher / Subscriber node build)
- 의존성 확인 후 target을 build
- 우리가 만든 message에서 사용할 C++, Javascript, Python...을 build (파란 글자)
Publisher node 선언 방식
- Execute file이 topic publisher와 subscriber로 구성되어있음을 의미
- source code, build, execute file 은 서로 분리되어 관리된다
- Execute file은 아래의 directory에 있음
8) Publisher Node 실행
$ roscore
$ rosrun ros_tutorials_topic topic_publisher
- Master node 실행 후 Publisher node 실행
- rosrun 명령어는 단일 node를 실행시킴
- ros launch는 launch file을 통해 여러 node들을 한번에 실행시킴
- second
- nano second
- data(0, 1, ...) 순으로 출력
- 위 결과는 실제로는 publish를 하는 것이 아닌, 단순히 출력만을 하는 것 (== ROS_INFO)
$ rqt_graph
$ rostopic list
- 현재 ROS 네트워크에서 사용중인 topic 목록 확인
$ rostopic echo /ros_tutorial_msg
- ros_tutorial_msg의 내용 확인
$ rostopic info /ros_tutorial_msg
- ros_tutorial_msg topic의 정보 확인
- 172.16.2.73은 local host의 ip, 41439는 port 번호
$ rostopic hz /ros_tutorial_msg
- ros_tutorial_msg topic의 주기(Hertz) 확인
$ rostopic bw /ros_tutorial_msg
- ros_tutorial_msg topic의 데이터 대역폭(Bandwidth) 확인
9) Subscriber Node 실행
$ rosrun ros_tutorials_topic topic_subscriber
- Subscriber node 실행
- Publisher node에서 publish한 message를 Subscriber node에서 subscribe하여 출력
$ rqt_graph
- 실행된 node들의 통신 상태 확인
- 요약
- topic_publisher라는 이름의 node가 ros_tutorials_topic 패키지의 MsgTutorial 메세지 파일을 ros_tutorial_pub라는 publisher를 통해 ros_topic_msg라는 토픽명으로 publish함
- topic_subscriber라는 이름의 node가 그 토픽을 ros_tutorial_sub라는 subscriber를 통해 받음
- ros_tutorials_topic::MsgTutorial::msg 내에는 'time' type의 stamp, 'int32' type의 data가 들어있음
- 아래 github에서 해당 내용에 대한 source code를 볼 수 있음
https://github.com/ROBOTIS-GIT/ros_tutorials/tree/master/ros_tutorials_topic
$ cd ~/catkin_ws/src
$ git clone https://github.com/ROBOTIS-GIT/ros_tutorials.git
$ cd ~/catkin_ws
$ catkin_make
- Service (Topic과 거의 비슷)
1) Package 생성
$ cd ~/catkin_ws/src
$ catkin_create_pkg ros_tutorials_service message_generation std_msgs roscpp
2) Package.xml 수정
$ cd ros_tutorials_service
$ gedit package.xml
- 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>
3) CMakeLists.txt 수정
$ gedit CMakeLists.txt
- 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})
4) Service 파일 작성
add_service_files(FILES SrvTutorial.srv)
- CMakeLists.txt에 위와 같이 node에서 사용할 message인 SrvTutorial.srv를 build시 포함하도록 함
$ roscd ros_tutorials_service
$ mkdir srv
$ cd srv
$ gedit SrvTutorial.srv
- SrvTutorial.srv에 아래 내용 작성
int64 a
int64 b
---
int64 result
- int64는 메세지 형식
- a, b는 서비스 요청 (service request). Service Client가 사용
- result는 서비스 응답 (service response). Service Server가 사용
- ---는 요청과 응답을 구분하는 구분자
5) Service Server Node 작성
add_executable(service_server src/service_server.cpp)
- CMakeLists.txt에서 src 폴더에 있는 service_server.cpp 라는 파일을 build하여 service_server라는 execution file을 만들도록 했음
$ roscd ros_tutorials_service/src
$ gedit service_server.cpp
- service_server.cpp 파일을 열고 아래 내용 입력
#include "ros/ros.h"
#include "ros_tutorials_service/SrvTutorial.h"
// SrvTutorial 서비스 파일 헤더 (빌드후 위에서 작성한 srv 파일을 기준으로 자동 생성됨)
// 서비스 요청이 있을 경우, 아래의 처리를 수행한다
// 서비스 요청은 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; // 서비스 응답을 하는 부분. true를 return해야 client가 error없이 받았다고 인식함
}
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;
}
6) Service Client Node 작성
add_executable(service_client src/service_client.cpp)
- CMakeLists.txt 파일에서 src 폴더에 있는 service_client.cpp 파일을 build하여 service_client라는 execution 파일을 만들게 했음
$ roscd ros_tutorials_service/src
$ gedit service_client.cpp
- service_client_cpp 파일을 열어 아래 내용 입력
#include "ros/ros.h"
#include "ros_tutorials_service/SrvTutorial.h" // SrvTutorial 서비스 파일 헤더 (빌드후 자동 생성)
#include <cstdlib> // atoll 함수 사용을 위한 라이브러리
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");
// service_client node 실행 후 서비스 요청으로 보낼 a, b를 받기 위해 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;
}
7) ROS Node Build
$ cd ~/catkin_ws && catkin_make
- catkin workspace로 이동 후 caktin build 실행
8) Service Server 실행
$ rosrun ros_tutorials_service service_server
- roscore 실행 후 새 터미널 창을 열어 Service Server 실행
- Service server는 service request가 있기 전까지 대기하도록 설정하였으므로 service request를 기다리게 된다
9) Service Client 실행
$ rosrun ros_tutorials_service service_client 2 3
[INFO] [1495726543.277216401]: send srv, srv.Request.a and b: 2, 3
[INFO] [1495726543.277258018]: receive srv, srv.Response.result: 5
- Service Client 실행 시 입력해준 실행 매개변수 2, 3을 service request 값으로 전송되도록 설정하였었음
- 2, 3은 각각 a, b값으로 service를 request하게 되고, 결괏값으로 2 + 3 = 5를 reponse값으로 전송받음
- 해당 tutorial에서는 실행 parameter로 이를 사용하였으나, 실제 활용에서는 command나 계산되어야 할 값, trigger용 변수 등을 service request값으로 사용할 수도 있다
- Service는 Topic과 달리 일회성 통신이므로 rqt_graph를 통해 확인할 수 없다 (한번 보내고 끝남)
- [참고1] rosservice call 명령어 사용 방법
- Service request를 위처럼 service client node 실행이 아닌 rosservice call 명령어를 통해 할 수 있다
$ rosservice call /ros_tutorial_srv 10 2
- [참고2] GUI 도구인 Service Caller 사용 방법
- rqt 실행 후 프로그램 메뉴에서 Plugins -> Service -> Service Caller를 선택
- 사용할 Service 이름을 선택
- a, b값을 타이핑 하여 설정한 후 좌측 상단의 'Call' 버튼을 눌러 response 확인
- 하나의 Node는 복수의 Publisher, Subscriber, Service Server, Service Client 역할을 할 수 있다
ros::NodeHandle nh;
ros::Publisher topic_publisher = nh.advertise<ros_tutorials::MsgTutorial>("ros_tutorial_msg", 100);
ros::Subscriber topic_subscriber = nh.subscribe("ros_tutorial_msg", 100, msgCallback);
ros::ServiceServer service_server = nh.advertiseService("ros_tutorial_srv", calculation);
ros::ServiceClient service_client = nh.serviceClient<ros_tutorials::SrvTutorial>("ros_tutorial_srv");
- 다만 위같이 node를 설정하면 혼자서만 값을 주고 받게 됨 (개념 설명만)
https://github.com/ROBOTIS-GIT/ros_tutorials/tree/master/ros_tutorials_service
- Service 내용에 대한 Source Code
- Parameter
- roscore 실행 시 ros master, rosout(ros관련 log 기록), parameter 역할을 함
- 그 중 parameter에 관한 내용
1) Parameter를 활용한 Node 작성
- 앞서 작성한 Service Server와 Client node의 service_server.cpp 를 수정하여 service request로 입력된 a와 b를 단순히 더하는 것이 아닌 사칙연산을 할 수 있게끔 parameter 활용
- 외부의 변수 parameter를 통해 내부의 process를 바꿈
$ roscd ros_tutorials_service/src
$ gedit service_server.cpp
- service server 파일을 아래와 같이 수정
- service client는 단순히 a, b를 보내는 역할만 하므로 변경하지 않음
#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 값을 파라미터 값(g_operator)에 따라 연산자를 달리한다.
// 계산한 후 서비스 응답 값에 저장한다
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); // 매개변수 초기설정(1로 초기화)
// 서비스 서버 선언, 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);
// 연산자를 parameter로부터 받은 값으로 변경한다
ros::spinOnce();
// 콜백함수 처리루틴
r.sleep();
// 루틴 반복을 위한 sleep 처리.
}
return 0;
}
- Parameter 관련 설정(setParam) 및 읽기(getParam) 사용법 주의
- [참고] 파라미터로 사용 가능한 형태
- 파라미터는 integers, floats, boolean, string, dictionaries, list 등으로 설정할 수 있다.
- 간단히 예를 들자면, 1은 integers, 1.0은 floats, “Internet of Things”은 string, true는 boolean, [1,2,3]은 integers의 list, a: b, c: d는 dictionary이다.
2) Node Build 및 실행
$ cd ~/catkin_ws && catkin_make
$ rosrun ros_tutorials_service service_server
- catkin build 후 service server node 실행
3) Parameter 목록 보기
$ rosparam list
- 현재 ROS 네트워크에 사용된 파라미터의 목록 확인
- 아래와 같은 결과가 뜸
/calculation_method
/rosdistro
/rosversion
/run_id
- /calculation_method가 위에서 사용한 parameter
4) Parameter 사용 예
$ rosservice call /ros_tutorial_srv 10 5 → 사칙연산의 변수 a, b 입력
result: 15 → 디폴트 사칙연산인 덧셈 결괏값
$ rosparam set /calculation_method 2 → 뺄셈
$ rosservice call /ros_tutorial_srv 10 5
result: 5
$ rosparam set /calculation_method 3 → 곱셈
$ rosservice call /ros_tutorial_srv 10 5
result: 50
$ rosparam set /calculation_method 4 → 나눗셈
$ rosservice call /ros_tutorial_srv 10 5
result: 2
- 같은 service request를 하며 parameter를 변경하여 service response가 달라짐을 확인
- Parameter는 Node 외부에서 node의 흐름이나 설정, 처리 등을 바꿀 수 있다 (매우 유용한 기능이므로 숙지 필요)
https://github.com/ROBOTIS-GIT/ros_tutorials/tree/master/ros_tutorials_parameter
- Parameter에 관한 Source file
- roslaunch 사용법
- rosrun : 하나의 node만을 실행
- roslaunch : 다수의 node 실행 가능
- roslaunch는 node 실행뿐만 아니라 node 실행시 package의 parameter나 node 이름 변경, node namespace 설정, ROS_ROOT 및 ROS_PACKAGE_PATH 설정, 환경 변수 변경 등의 option을 붙일 수 있는 ROS 명령어이다.
$ roslaunch [패키지명] [roslaunch 파일]
- tag별 옵션을 제공하는 XML기반의 '.launch' file을 실행하며 해당 file은 package folder내 'launch' folder 안에 있어야 한다
1) roslaunch의 활용
- 앞서 작성한 topic_publisher와 topic_subscriber node의 이름을 바꿔 실행
$ roscd ros_tutorials_topic
$ mkdir launch
$ cd launch
$ gedit union.launch
- ros_tutorials_topic package directory로 이동 후 launch directory 생성 후 그곳으로 이동하여 launch file 생성
- union.launch는 위의 topic 관련 github source code에 있는 launch file
<launch>
<node pkg="ros_tutorials_topic" type="topic_publisher" name="topic_publisher1"/>
<node pkg="ros_tutorials_topic" type="topic_subscriber" name="topic_subscriber1"/>
<node pkg="ros_tutorials_topic" type="topic_publisher" name="topic_publisher2"/>
<node pkg="ros_tutorials_topic" type="topic_subscriber" name="topic_subscriber2"/>
</launch>
- union.launch 파일
- <launch> 태그 안에는 roslaunch 명령어로 노드를 실행할 때 필요한 태그들이 기술됨
- <node>는 roslaunch로 실행할 노드를 기술. 옵션으로는 pkg, type, name이 있다.
- pkg : 패키지의 이름
- type : 실제 실행할 노드의 이름(노드명)
- name : 위 type에 해당하는 노드가 실행될 때 붙여지는 이름(실행명), 일반적으로는 type과 같게 설정하지만 필요에 따라 실행할 때 이름을 변경하도록 설정할 수 있다.
- 위의 경우 각 subscriber와 publisher node의 구분을 위해 이름을 다르게 함
$ roslaunch ros_tutorials_topic union.launch --screen
- union.launch 파일 실행
- --screen은 해당 터미널에 실행되는 모든 node들의 출력들이 표시되게 하는 option
$ rosnode list
- 실행 node확인
/topic_publisher1
/topic_publisher2
/topic_subscriber1
/topic_subscriber2
/rosout
- 확인 결과 4개의 node가 정상적으로 실행되고 이름도 의도한데로 바뀜
- 그런데 rqt로 확인한 결과 subscriber node가 두 publisher node의 message를 모두 subscribe 하고 있음
- launch file에서 단순히 node의 이름만을 변경하고 사용 message의 이름을 변경하지 않아 생기는 문제
- 다른 roslaunch namespace tag를 사용하여 해결 가능
$ roscd ros_tutorials_service/launch
$ gedit union.launch
- union.launch 파일을 아래와 같이 수정
<launch>
<group ns="ns1">
<node pkg="ros_tutorials_topic" type="topic_publisher" name="topic_publisher"/>
<node pkg="ros_tutorials_topic" type="topic_subscriber" name="topic_subscriber"/>
</group>
<group ns="ns2">
<node pkg="ros_tutorials_topic" type="topic_publisher" name="topic_publisher"/>
<node pkg="ros_tutorials_topic" type="topic_subscriber" name="topic_subscriber"/>
</group>
</launch>
- <group>은 실행되는 node를 group으로 묶을 때 사용
- 위의 경우 'ns' option (name space)을 통해 그룹의 이름을 ns1, ns2로 지칭함
- 그룹에 속한 node의 이름과 message 등도 모두 ns로 지정한 이름에 포함됨
- Topic과 node의 이름이 변경됨
<launch>
<arg name="update_ period" default="10" />
<param name="timing" value="$(arg update_ period)"/>
</launch>
- <arg>를 통해 launch file 내부에 "update_period" 변수를 정의 (기본값 : 10)
$ roslaunch my_package my_package.launch update_ period:=30
- roslaunch를 통해 외부에서 parameter 값을 변경 (주기)
https://github.com/ROBOTIS-GIT/ros_tutorials
- 이번 강좌의 repository
https://www.youtube.com/watch?v=iGdQHi_wL1Y&list=PLRG6WP3c31_VIFtFAxSke2NG_DumVZPgw&index=9
https://changun516.tistory.com/134
https://enssionaut.com/board_robotics/974
https://velog.io/@717lumos/roslaunch%EC%9D%98-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%8F-XML
https://changun516.tistory.com/134
'Study_ROS > [ROBOTIS ROS Courses]' 카테고리의 다른 글
[ROBOTIS ROS Courses] Ch11. SLAM과 Navigation (0) | 2023.01.24 |
---|---|
[ROBOTIS ROS Courses] Ch8. 로봇, 센서, 모터 ~ Ch10. 모바일 로봇 (0) | 2023.01.24 |
[ROBOTIS ROS Courses] Ch6. ROS 도구(RViz, rqt, Gazebo) (0) | 2022.09.17 |
[ROBOTIS ROS Courses] Ch5. ROS 명령어 + 용어 정리 (0) | 2022.09.10 |
[ROBOTIS ROS Courses] Ch3. ROS 개발환경 구축 ~ Ch4. ROS의 중요 컨셉 (0) | 2022.09.10 |