본문 바로가기

카테고리 없음

[2024 ROS 스터디] 정성진 #3주차 - ROS 기본 프로그래밍

반응형

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는 하나 이상의 정해진 노드를 실행하는 명령어이다.

 

반응형