본문 바로가기

FOSCAR-(Autonomous Driving)/ROS 스터디

[2023 ROS 스터디] 박준석 #3주차 - ROS 기본 프로그래밍

반응형

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

http://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

표준 단위: SI단위 사용,

좌표 표현 방식 - x: forward, y: left, z: up

                        - 오른손 법칙

프로그래밍 규칙은 위의 사이트에 자세히 기술되어있다.

 

#Topic/ Publisher/ Subscriber

-ROS에서는 단방향 통신일때 'Topic'이라는 메시지 통신을 사용한다. 이떄 송신 측을 'Publisher', 수신 측을 'Subscriber'라고 부른다. 

 

1. 패키지 생성

$ catkin_create_pkg ros_tutorials_topic message_generation std_msgs roscpp

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

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

$ gedit package.xml

위의 코드를 치면 하나의 메모장이 나올것이다. 그 메모장에 아래의 내용을 넣자.

<? 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 type="repository">https://github.com/ROBOTIS-GIT/ros_tutorials.git >
<url type="bugtracker">https://github.com/ROBOTIS-GIT/ros_tutorials/issues > 
<buildtool_depend>catkin</buildtool_depend>
<depend>roscpp</depend>
<depend>std_msgs</depend>
<depend>message_generation</depend>
<export></export >
</package>

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

cmake_minimum_required(VERSION 2.8.3) 
project(ros_tutorials_topic) 

## 캐빈 빌드를 할 때 요구되는 구성요소 패키지이다.
## 의존성 패키지로 message_generation, std_msgs, roscpp이며 이 패키지들이 
## 존재하지 않으면 빌드 도중에 에러가 난다.
find_package(catkin REQUIRED COMPONENTS message_generation std_msgs roscpp)

## 메시지 선언: MsgTutorial.msg
add_message_files(FILES MsgTutorial.msg)

## 의존하는 메시지를 설정하는 옵션이다.
## std_msgs가 설치되어 있지 않다면 빌드 도중에 에러가 난다.
generate_messages(DEPENDENCIES std_msgs)

## 캐긴 패키지 옵션으로 라이브러리, 캐킨 빌드 의존성, 시스템 의존 패키지를 기술한다.
catkin_package(
LIBRARIES ros_tutorials_topic  
CATKIN_DEPENDS std_msgs roscpp
)

## 인클루드 디렉터리를 설정한다.
include_directories(${catkin_INCLUDE_DIRS})  

## topic_publisher 노드에 대한 빌드 옵션이다.
## 실행 파일, 타깃 링크 라이브러리, 추가 의존성 등을 설정한다.
add_executable(topic_publisher src/topic_publisher.cpp)
add_dependencies(topic_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} 
${catkin_EXPORTED_TARGETS})
target_link_libraries(topic_publisher ${catkin_LIBRARIES})

 

## topic_subscriber 노드에 대한 빌드 옵션이다.
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. 메시지 파일 설정 

- 앞서 CMakeLists.txt 파일에 다음과 같은 옵션을 넣었다.

add_executable(topic_publisher src/topic_publisher.cpp)

노드에서 사용할 메시지인 MsgTutorial.msg를 빌드할 때 포함하라는 이야기

$ rosed ros_tutorials_topic 
$ mkdir msg
$ cd msg 
$ gedit MsgTutorial.msg

MsgTutorial.msg에 아래의 내용을 붙여넣기 후 저장

time stamp 
int32 data

5. 퍼블리셔 노드 작성

앞서 CMakeLists.list파일에 다음과 같은 실행 파일을 생성하는 옵션을 주었다.

add_executable(topic_publisher src/topic_publisher.cpp)

src 폴더의 topic_publisher.cpp라는 파일을 빌드하여 topic_publisher라는 실행파일을 만들어야한다.

$ cd src
$ gedit topic_publisher.cpp

아무것도 없는 파일(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 시스템과 통신을 위한 노드 핸들 선언

// 퍼블리셔 선언(이 노드는 퍼블리셔의 역할을 한다고 선언해 주는것),
// 퍼블리셔 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;
}

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

앞서 CMakeLists.txt파일에 다음과 같은 실행 파일을 생성하는 옵션을 준다.

add_executable(topic_subscriber src/topic_subscriber.cpp)

topic_subscriber.cpp라는 파일을 빌드한 후 topic_subscriber라는 실행 파일을 만들고 

$ rosed 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;
}

앞서 CMakeLists.list파일에 다음과 같은 실행 파일을 생성하는 옵션을 주었다.

 

7. ROS 노드 빌드 

다음 명령어로 ros_tutorials_topic 패키지의 메시지 파일, 퍼블리셔 노드, 서브스크라이버 노드를 빌드한다.

$ cd ~/catkin_ws
$ catkin_make

8. 퍼블리셔 실행 

 아래의 명령어를 통해 topic_publisher 노드를 구동해보자

$ rosrun ros_tutorials_topic topic_publisher

 

rostopic list를 실행시키면 ros_tutorial_msg라는 토픽이 있는 것도 확인 가능

$ rostopic list

9. 서브스크라이버 실행

$ rosrun ros_tutorials_topic topic_subscriber

10) 실행된 노드들의 통신 상태 확인

rqt_graph

rqt_graph

위의 그림처럼 잘 연결되어 있는것을 확인할 수 있다.

 

 

#Service / Service  server / Service client

1.패키지 생성

$ catkin_create_pkg ros_tutorials_service message_generation std_msgs roscpp

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

위에서 #Topic/ Publisher/ Subscriber할 때랑 name빼고 모두 동일하다.

<? xml version="1.0"?>  
<package format="2"> 
<name>ros_tutorials_service</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 type="repository">https://github.com/ROBOTIS-GIT/ros_tutorials.git >
<url type="bugtracker">https://github.com/ROBOTIS-GIT/ros_tutorials/issues > 
<buildtool_depend>catkin</buildtool_depend>
<depend>roscpp</depend>
<depend>std_msgs</depend>
<depend>message_generation</depend>
<export></export >
</package>

3. 빌드 설정 파일(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})

## topic_subscriber 노드에 대한 빌드 옵션이다.
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.서비스 파일 작성 

CMakeLists.txt파일에 다음과 같은 옵션을 넣자

add_service_files(FILES SrvTutorial.srv)

노드에서 사용할 메시지인 SrvTutorials.srv를 빌드할 때 포함해야한다.

$ rosed ros_tutorials_service
$ mkdir srv
$ cd srv
$ gedit SrvTutorial.srv

아래의 내용을 복붙하자 . (---) 는 구분자이다 

int64 a
int64 b
---
int64 result

5. 서비스 서버 노드 작성 

CMakeLists.txt파일에 다음과 같은 실행 파일을 생성하는 옵션을 주자

add_executable(service_server src/service_server.cpp)

src폴더의 service_server.cpp파일을 신규작성, 내용 수정하자 

$ cd ros_tutorials_service/src
$ gedit service_server.cpp

밑의 코드 복붙 후 저장

#include "ros/ros.h"                          // ROS Default Header File
#include "ros_tutorials_service/SrvTutorial.h"// SrvTutorial Service File Header (Automatically created after build)

// The below process is performed when there is a service request
// The service request is declared as 'req', and the service response is declared as 'res'
bool calculation(ros_tutorials_service::SrvTutorial::Request &req,
                 ros_tutorials_service::SrvTutorial::Response &res)
{
  // The service name is 'ros_tutorial_srv' and it will call 'calculation' function upon the service request.
  res.result = req.a + req.b;

  // Displays 'a' and 'b' values used in the service request and
  // the 'result' value corresponding to the service response
  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)              // Node Main Function
{
  ros::init(argc, argv, "service_server");   // Initializes Node Name
  ros::NodeHandle nh;                        // Node handle declaration

  // Declare service server 'ros_tutorials_service_server'
  // using the 'SrvTutorial' service file in the 'ros_tutorials_service' package.
  // The service name is 'ros_tutorial_srv' and it will call 'calculation' function
  // upon the service request.
  ros::ServiceServer ros_tutorials_service_server = nh.advertiseService("ros_tutorial_srv", calculation);

  ROS_INFO("ready srv server!");

  ros::spin();    // Wait for the service request

  return 0;
}

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

CMakeLists.txt파일에 다음과 같은 실행 파일을 생성하는 옵션을 주자

add_executable(service_client src/service_client.cpp)

src폴더의 service.client.cpp파일 신규 작성 및 내용 수정

$ cd ros_tutorials_service/src
$ gedit service.client.cpp

아래의 코드 복붙 후 저장

#include "ros/ros.h"                          // ROS Default Header File
#include "ros_tutorials_service/SrvTutorial.h"// SrvTutorial Service File Header (Automatically created after build)
#include <cstdlib>                            // Library for using the "atoll" function

int main(int argc, char **argv)               // Node Main Function
{
  ros::init(argc, argv, "service_client");    // Initializes Node Name

  if (argc != 3)  // Input value error handling
  {
    ROS_INFO("cmd : rosrun ros_tutorials_service service_client arg0 arg1");
    ROS_INFO("arg0: double number, arg1: double number");
    return 1;
  }

  ros::NodeHandle nh;       // Node handle declaration for communication with ROS system

  // Declares service client 'ros_tutorials_service_client'
  // using the 'SrvTutorial' service file in the 'ros_tutorials_service' package.
  // The service name is 'ros_tutorial_srv'
  ros::ServiceClient ros_tutorials_service_client = nh.serviceClient<ros_tutorials_service::SrvTutorial>("ros_tutorial_srv");

  // Declares the 'srv' service that uses the 'SrvTutorial' service file
  ros_tutorials_service::SrvTutorial srv;

  // Parameters entered when the node is executed as a service request value are stored at 'a' and 'b'
  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);

  // Request the service. If the request is accepted, display the response value
  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노드 빌드 

다음 명령어로 ros_tutorials_service 패키지의 서비스 파일, 서비스 서버 노드와 클라이언트 노드를  빌드하자 

$ cd ~/catkin_ws && catkin_make

8. 서비스 서버 실행 

서비스 서버는 서비스 요청이 있기 전까지 아무런 처리를 하지 않고 기다리도록 프로그래밍하였다. 그러므로 다음 명령어를 실행하면서 서비스 서버는 서비스 요청을 기다린다.

$ rosrun ros_tutorials_service service_server

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

서비스 서버 실행하면 서비스 클라이언트를 실행해보자

$ rosrun ros_tutorials_service service_client 2 3

< [참고] GUI 도구인 Service Caller 사용 방법  >

$ rqt

< [참고] rosservice call 명령어 사용 방법 >

서비스 요청은 서비스 클라이언트 노드를 실행하는 방법도 있지만, rosservice call이라는 명령어도 있다.

$ rosservice call/ros_tutorial_srv 2 3

< [참고] rqt_graph  >

서비스는 일회성이므로 rqt_graph 등에서 확인할 수 없다.

 

#Parameter

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

service_server.cpp 소스를 수정하자 

$ rosed ros_tutorials_server/src
$ gedit service_server.cpp
#include "ros/ros.h"                            // ROS Default Header File
#include "ros_tutorials_parameter/SrvTutorial.h"// action Library Header File

#define PLUS            1   // Addition
#define MINUS           2   // Subtraction
#define MULTIPLICATION  3   // Multiplication
#define DIVISION        4   // Division

int g_operator = PLUS;

// The process below is performed if there is a service request
// The service request is declared as 'req', and the service response is declared as 'res'
bool calculation(ros_tutorials_parameter::SrvTutorial::Request &req,
                 ros_tutorials_parameter::SrvTutorial::Response &res)
{
  // The operator will be selected according to the parameter value and calculate 'a' and 'b',
  // which were received upon the service request.
  // The result is stored as the Response value.
  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;
  }

  // Displays the values of 'a' and 'b' used in the service request, and the 'result' value
  // corresponding to the service response.
  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)             // Node Main Function
{
  ros::init(argc, argv, "service_server");  // Initializes Node Name
  ros::NodeHandle nh;                       // Node handle declaration

  nh.setParam("calculation_method", PLUS);  // Reset Parameter Settings

  // Declare service server 'service_server' using the 'SrvTutorial' service file
  // in the 'ros_tutorials_service' package. The service name is 'ros_tutorial_srv' and
  // it is set to execute a 'calculation' function when a service is requested.
  ros::ServiceServer ros_tutorials_service_server = nh.advertiseService("ros_tutorial_srv", calculation);

  ROS_INFO("ready srv server!");

  ros::Rate r(10);  // 10 hz

  while (ros::ok())
  {
    nh.getParam("calculation_method", g_operator);  // Select the operator according to the value received from the parameter.
    ros::spinOnce();  // Callback function process routine
    r.sleep();        // Sleep for routine iteration
  }

  return 0;
}

2. 노드 빌드 및 실행

$ cd ~/catkin_ws && catkin_make
$ rosrun ros_tutorials_service service_server

3. 매개변수 목록 보기 

ROS 네트워크에 사용된 파라미터의 목록을 확인 가능

$ rosparam list

4.파라미터 사용 예

$ rosservie call/ros_tutorial_srv 10 5

결과값이 15가 나오는 것을 알 수 있다.

#roslaunch

rosrun은 하나의 노드를 실행!

roslaunch는 하나 이상의 정해진 노드 실행 !

실행 명령어는 아래와 같다 

$ roslaunch 패키지명 roslaunch파일

1. roslaunch의 활용

이전에 작성한 topic_publisher와 topic_subscriber 노드의 이름을 바꾸어서 실행 해 보자. 이름만 바꾸면 의미가 없으니, 퍼블리쉬 노드와 서브스크라이버 노드를 각각 두 개씩 구동하여 서로 별도의 메시지 통신을 해보자.

변경 전
변경 후

 

 

이때는 토픽 명과 노드명이 달라지게 된다. 

 

2. launch 태그 

 

반응형