본문 바로가기

FOSCAR-(Autonomous Driving)/ROS 스터디

[2024 ROS 스터디] 곽우인 #3주차 - ROS 기본 프로그래밍

반응형

ROS 프로그래밍 전에 알아둬야 할 사항

1. 표준단위로 SI 단위 사용

2. 좌표 표현 방식

x: forward y: left z: up

오른손법칙

3. 회전할때 시계 반대방향이 +, 시계방향이 -

 

표준 단위

angle: radian / frequency: hertz / force: newton / power: watt / voltage: volt / length: meter / mass: kiligram

time: second / current: ampere / temperature: celsius

 

프로그래밍 규칙은 위키 참조

https://wiki.ros.org/CppStyleGuide

 

CppStyleGuide - ROS Wiki

ROS C++ Style Guide This page defines a style guide to be followed in writing C++ code for ROS. This guide applies to all ROS code, both core and non-core. For Python, see the PyStyleGuide and for Javascript, see the ROS JavaScript Style Guide For general

wiki.ros.org

 

ROS 기본 프로그래밍

 

Topic

 

1) 패키지 생성

$cd ~/catkin_ws/src

(~home폴더에 있는 자기 이름 폴더)에 있는 catkin_ws폴더에 있는 패키지를 모아놓은 폴더로 이동.

$catkin_create_pkg ros_tutorials_topic message_generation std_msgs roscpp

ros_tutorials_topic이라는 패키지를 만들고 이 패키지는 message_generation, std_msgs, roscpp에 의존성을 갖는다.

$cd ros_tutorials_topic

이 폴더로 이동

$ls

리스트를 뜻하고 폴더안에 있는 파일들을 보여준다.

 

2) 패키지 설정 파일(package.xml) 수정

ROS의 필수 설정 파일 중 하나인 package.xml은 패키지 정보를 담은 XML 파일로서 패키지 이름, 저작자, 라이선스, 의존성 패키지 등을 기술하고 있다.

$gedit package.xml

gedit는 우분투의 가장 기본적인 메모장

<?xml version="1.0"?>

<package format="2">

<name>ros_tutorials_topic</name> //패키지 이름

<version>0.1.0</version>

<description>ROS turtorial package to learn the topic</description> // 패키지의 간략한 설명을 넣어주면 된다. (위키에서 검색하는데 사용돼서 중요하다.)

<license>Apache 2.0</license>

<author email="pyo@robotis.com">Yoonseok Pyo</author>

<maintainer email="pyo@robotis.com">Yoonseok Pyo</maintainer>

<url type="website">http://www.robotis.com</url>

<url type="repository">https://github.com/ROBOTIS-GIT/ros_tutorials.git</url> // 소스코드 주소

<url type="bugtracker">https://github.com/ROBOTIS-GIT/ros_tutorials/issues</url > // 버그 제보 주소

<buildtool_depend>catkin</buildtool_depend>

<depend>roscpp</depend> // C++를 사용한다는 뜻

<depend>std_msgs</depend> // standard한 메시지를 사용할거다.

<depend>message_generation</depend> // 새로운 메시지를 만들거다.

<export></export >

</package>

 

3) 빌드 설정 파일(CMakeLists.txt) 수정

$ gedit CMakeLists.txt

실제 노드를 빌드할 때 즉 소스코드를 작성하고 실행파일을 만들 때 사용되는 옵션을 작성해놓은 것.

cmake_minimum_required(VERSION 2.8.3)

project(ros_tutorials_topic)

패키지랑 동일한 이름으로 해야한다.

find_package(catkin REQUIRED COMPONENTS message_generation std_msgs roscpp)

add_message_files(FILES MsgTutorial.msg)

새로 만들것이기 때문에 새로 만들 메시지 이름을 쓰기

generate_messages(DEPENDENCIES std_msgs)

catkin_package(

LIBRARIES ros_tutorials_topic

CATKIN_DEPENDS std_msgs roscpp

)

## 인클루드 디렉터리를 설정한다.

include_directories(${catkin_INCLUDE_DIRS})

뒤에 include붙이면 본인 include파일 사용 가능

add_executable(topic_publisher src/topic_publisher.cpp)

topic_publisher라는 노드를 만들꺼고 그 이름을 갖는 노드를 만들 때 참고하는 소스코드는 src/topic_publisher.cpp이다.

add_dependencies(topic_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} 

헤더파일 먼저 빌드하고 topic_publisher 메시지를 빌드한다는 뜻

${catkin_EXPORTED_TARGETS})

target_link_libraries(topic_publisher ${catkin_LIBRARIES})

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) 메시지 파일 작성

$ roscd ros_tutorials_topic

roscd하면 그 뒤의 패키지 이름이 있는곳으로 이동

$ mkdir msg

ros_tutorials_topic 패키지에 msg라는 메시지 폴더를 신규 작성

$ cd msg

작성한 msg 폴더로 이동

$ gedit MsgTutorial.msg

MsgTutorial.msg 파일 신규 작성 및 내용 수정

여기에 time stamp, int32 data 이 두 개의 메시지 형을 넣어준다.

$ cd .. ros_tutorials_topic 패키지 폴더로 이동

 

5) 퍼블리셔 노드 작성

$ cd src  //ros_tutorials_topic 패키지의 소스 폴더인 src 폴더로 이동

$ gedit topic_publisher.cpp  // 소스 파일 신규 작성 및 내용 수정

켜진 gedit에 다음 내용을 넣으면 된다.

#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 시스템과 통신을 위한 노드 핸들 선언

노드 핸들을 선언한다는것은 특정 노드와 관련된 ROS 리소스(퍼블리셔, 서브스크라이버)등을 관리하고 접근하기 위한 인터페이스를 생성하는 것을 의미한다. 이를 통해 노드는 다른 노드와 데이터를 주고받을 수 있으며, 다양한 ROS 기능을 활용할 수 있게된다.

// 퍼블리셔 선언, 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;

}

위의 코드에서 ROS_INFOprintf로 보면 된다.

 

6) 서브스크라이버 노드 작성

$ roscd ros_tutorials_topic/src

패키지의 소스 폴더인 src 폴더로 이동

$ gedit topic_subscriber.cpp

소스 파일 신규 작성 및 내용 수정

켜진 gedit에 다음 내용을 넣으면 된다.

#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;

}

 

7) ROS 노드 빌드

$ cd ~/catkin_ws

catkin 폴더로 이동

$ catkin_make

catkin 빌드 실행

두 줄을 $ cm 으로 대체 가능하다.

 

8) 퍼블리셔 실행

$ rosrun ros_tutorials_topic topic_publisher

 

별개로

$ rostopic list //현재 ros 네트워크상에 모든 topic을 리스트

$ rostopic info /ros_tutorial_msg // 메시지의 타입과 누가 퍼블리시 하고있는지 서브스크라이브 하고있는지 표시

 

9) 서브스크라이버 실행

$ rosrun ros_tutorials_topic topic_subscriber

 

퍼블리셔와 서브스크라이버를 실행하고 rqt_grqph를 해보았다.

 

Service

 

1) 패키지 생성

$ cd ~/catkin_ws/src

$ catkin_create_pkg ros_tutorials_service message_generation std_msgs roscpp

$ cd ros_tutorials_service

$ ls

 

2) 패키지 설정 파일(package.xml) 수정

$ 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>

<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

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) 서비스 파일 작성

$ roscd ros_tutorials_service

패키지 폴더로 이동

$ mkdir srv

패키지에 srv라는 서비스 폴더를 신규 작성

$ cd srv

작성한 srv 폴더로 이동

$ gedit SrvTutorial.srv

SrvTutorial.srv 파일 신규 작성 및 내용 수정

geditrequest 부분인

int64 a

int64 b

--- <- requestresponse를 구분하는 구분자

response 부분인

int64 result 나눠서 작성하기.

 

5) 서비스 서버 노드 작성

$ roscd ros_tutorials_service/src 패키지의 소스 폴더인 src 폴더로 이동

$ 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)

{

// 서비스 요청시 받은 ab 값을 더하여 서비스 응답 값에 저장한다

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;

}

 

6) 서비스 클라이언트 노드 작성

$ roscd ros_tutorials_service/src 패키지의 소스 폴더인 src 폴더로 이동

$ gedit service_client.cpp 소스 파일 신규 작성 및 내용 수정

#include "ros/ros.h" // ROS 기본 헤더 파일

#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");

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 노드 빌드

$ cd ~/catkin_ws && catkin_make

 

8) 서비스 서버 실행

$ rosrun ros_tutorials_service service_server

 

9) 서비스 클라이언트 실행

$ rosrun ros_tutorials_service service_client 2 3

2와 3은 다른 숫자를 넣어도 상관없다.

**서비스는 토픽과는 달리 일회성이므로 rqt_graph를 통해서 확인할 수 없다.

 

Parameter

 

1) 파라미터를 활용한 노드 작성

$ roscd ros_tutorials_service/src

패키지의 소스 코드 폴더인 src 폴더로 이동

$ gedit 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;

맨 처음 연산을 PLUS를 기준으로 한다.

// 서비스 요청이 있을 경우, 아래의 처리를 수행한다

// 서비스 요청은 req, 서비스 응답은 res로 설정하였다

bool calculation(ros_tutorials_service::SrvTutorial::Request &req,

ros_tutorials_service::SrvTutorial::Response &res)

{

// 서비스 요청시 받은 ab 값을 파라미터 값에 따라 연산자를 달리한다.

// 계산한 후 서비스 응답 값에 저장한다

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;

}

 

2) 노드 빌드 및 실행

$ cd ~/catkin_ws && catkin_make

$ rosrun ros_tutorials_service service_server

 

roslaunch 사용법

rosrun: 하나의 노드를 실행하는 명령어이다.

roslaunch: 하나 이상의 정해진 노드를 실행시킬 수 있다.

그 밖의 기능으로 노드를 실행할 때 패키지의 매개변수나 노드 이름 변경, 노드 네임스페이스 설정, ROS_ROOT ROS_PACKAGE_PATH 설정, 환경 변수 변경 등의 옵션을 붙일 수 있는 ROS 명령어이다.

roslaunch 명령어를 쓸 때 screen 옵션을 추가해주면 해당 터미널에 실행되는 모든 노드들의 출력들이 터미널 스크린에 표시되기 때문에 쉽게 디버깅 할 수 있게된다.

반응형