그럼 node 특성을 결정짓는 parameter는 코드로 어떻게 짤까?

IROBOU·2024년 2월 25일
1

인공지능_로봇개발

목록 보기
15/17
post-thumbnail

ROS2에서 parameter는 어떻게 코드로 구현할 수 있을까?

오늘은 그 비밀에 대해 파헤쳐보자!


<목표>
C++로 class(node)내에 parameter를 정의하고 실행하는 실습을 진행해본다.

<예상 소요 시간>
약 20분

<영상 tutorial>


배경지식

지난 시간의 NAVER LABS의 자율주행 로봇에서 수많은 node를 켤 수 있는 launch파일에 대해 설명했었다.(관련 영상)
이렇게 launch파일로 node를 실행할 때 node의 기능이 내가 정한 형태로 동작하게 하고 싶을 때가 있다.

이는 parameter라는 것으로 조절할 수 있는 경우가 많다.

예를 들어 자율주행 로봇의 이동속도나 장애물을 얼마나 멀리로부터 감지해서 피할 것인지 등이 parameter에 해당하며 이 외에도 수많은 파라미터를 로봇은 가지고 있다.

이번 시간에는 이런 parameter를 C++ class(node 생성을 담당하는 class)에서 구현해보고 launch파일에서 어떻게 설정해주는지 실습해보자.


준비물


실습

실습 1. 패키지 생성하기

'cpp_paramters'라는 이름을 갖고 rclcpp에 의존하는 package를 하나 생성하자.

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_parameters --dependencies rclcpp

해당 패키지는 이번 시간에 우리가 parameter를 구현하고 설정하는데 사용할 예정이다.


실습 1.1. pakcage.xml 수정해주기

지금 단계에서 필수는 아니지만 배포(누군가 내 패키지를 사용하게 뿌리는 행위)할 때를 대비해서 package.xml에 들어가서 description과 maintainer, license등을 다음과 같이 변경해주자.

<description>C++ parameter tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

maintainer의 이름은 적절히 본인의 email을 작성하면 된다.


실습 2. C++ node 작성하기

이제 본격적으로 C++ node를 작성해보자.

먼저 cpp_parameters_node.cpp파일을 패키지내 src 경로에 생성한다.

cd ~/ros2_ws/src/cpp_parameters/src
gedit cpp_parameters_node.cpp

cpp_parameters_node.cpp에 다음과 같은 코드를 작성하도록 하자.

#include <chrono>
#include <functional>
#include <string>

#include <rclcpp/rclcpp.hpp>

using namespace std::chrono_literals;

class MinimalParam : public rclcpp::Node
{
public:
  MinimalParam()
  : Node("minimal_param_node")
  {
    this->declare_parameter("my_parameter", "world");

    timer_ = this->create_wall_timer(
      1000ms, std::bind(&MinimalParam::timer_callback, this));
  }

  void timer_callback()
  {
    std::string my_param = this->get_parameter("my_parameter").as_string();

    RCLCPP_INFO(this->get_logger(), "Hello %s!", my_param.c_str());

    std::vector<rclcpp::Parameter> all_new_parameters{rclcpp::Parameter("my_parameter", "world")};
    this->set_parameters(all_new_parameters);
  }

private:
  rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalParam>());
  rclcpp::shutdown();
  return 0;
}

2.1. 코드 분석

  • 항상 그렇듯이 처음에 cpp가 필요로 하는 package와 library를 include해준다.
  • (참고) chrono, functional, string은 C++표준 라이브러리에 속하는 헤더파일이다. rclcpp는 ROS2 패키지이며 rclcpp/rclcpp.hpp는 패키지 내 library의 헤더파일에 해당한다.

  • std::chrono_literals라는 namespace를 지정해서 1000ms(1000밀리 세컨드, 1초)등의 표현을 사용한다.

  • class 생성자(node를 생성을 담당하는 부분)에서 "declare_parameter"를 통해 "my_parameter"라는 파라미터를 기본값 "world"로 생성한다.

  • 그다음 타이머를 생성하고 1000ms(1초)에 한번씩 주기적으로 timer_callback이라는 함수가 호출될 수 있도록 설정해준다. timer_callback은 하단에 따로 정의된다.

  • 이제 timer_callback을 정의할 차례이다. parameter를 구현하는데 필수는 아니지만 이 실습에서는 paramter값으로 우리가 어떤 기능을 구현할 수 있는지 보여주기 위해 구현된 부분이라고 생각하면된다.

  • timer_callback에서는 다음과 같은 동작이 수행된다.

  1. "my_parameter"의 파라미터 값을 획득해 my_param이라는 변수에 저장한다.
  2. 이 변수가 정확하게 획득됐는지 확인차 log를 띄운다.
  3. set_paramters라는 함수로 parameter을 세팅해주기위해서 먼저 all_new_paramters라는 벡터타입의 변수를 선언한다.
  4. 벡터의 첫 번째 원소값은 rclcpp::Paramter타입이며 이 타입은 파라미터 이름과 그 파라미터의 값을 이용해서 초기화 될 수 있다.
  5. 즉 "all_new_paramters안에 my_parameter라는 파라미터는 world라는 값을 가져"라고 말해주는 vector가 정의됐다고 생각하면된다.
  6. 이 vector를 이용해서 ser_paramters로 "my_prameter"의 값을 world로 만들어준다.

이때 의문이 들 수 있는데, "아니 이 행위를 왜하는거지?"라는 생각이 들 수 있다.

이따 실습에서 보겠지만 "my_paramter"라는 파라미터의 값이 혹시 사용자에 의해 외부에서 바뀔시 "world"라는 값으로 다시 바꿔버리기 위해 있는 코드이다.

실제 실습에서 확인하기로 하고 나머지 코드를 분석하자.

  • 다음은 private로 timer를 선언해준다. 위 코드에서 사용하기 위함이다.

  • 마지막으로 main함수에서 우리가 정의한 node가 실행될 수 있도록 객체화하는 동시에 spin으로 node가 켜져있을 수 있도록 코드를 작성한다.

  • Ctrl+C와 같은 중지 명령이 들어오면 node를 shutdown하고 0을 반환 후 프로그램을 종료한다.


2.1.1. (선택사항) Add ParamterDescriptor

조금 번거롭긴하지만 해놓으면 누군가 내가 생성한 paramter를 사용할 때 도움이 될 수 있는 코드 기능이 있다.

바로 내가 작성한 paramter가 어떤 역할을 담당하는지 묘사하는 설명을 달아 놓는 것이다.

예를 들어 다음과 같은 코드를 살펴보자.

// ...

class MinimalParam : public rclcpp::Node
{
public:
  MinimalParam()
  : Node("minimal_param_node")
  {
    auto param_desc = rcl_interfaces::msg::ParameterDescriptor{};
    param_desc.description = "This parameter is mine!";

    this->declare_parameter("my_parameter", "world", param_desc);

    timer_ = this->create_wall_timer(
      1000ms, std::bind(&MinimalParam::timer_callback, this));
  }

위의 코드는 우리가 실습 2.에서 작성한 코드와 거의 유사하지만 자세히 보면

    auto param_desc = rcl_interfaces::msg::ParameterDescriptor{};
    param_desc.description = "This parameter is mine!";

해당 부분이 추가로 작성되어 있고 declare_paramter에서도 param_desc가 추가된 것을 볼 수 있다.
이는 "my_paramter"에 대한 설명을 추가한 것이라고 보면 된다.

이렇게 설명을 추가해놓으면 나중에 다음과 같은 명령어를 사용하여 터미널에서 파라미터의 설명을 확인할 수 있다.

ros2 param describe /minimal_param_node my_parameter

(예시)


2.2. executable 추가

코드 작성도 끝났겠다 이제 CMakeLists.txt 파일에 적절히 executable을 추가해주자.

add_executable(minimal_param_node src/cpp_parameters_node.cpp)
ament_target_dependencies(minimal_param_node rclcpp)

install(TARGETS
    minimal_param_node
  DESTINATION lib/${PROJECT_NAME}
)


실습 3. 빌드 및 실행

이제 빌드하고 실행해보자

  • 먼저 혹시 빠뜨린 ros dependency가 있는지 체크해서 설치한다.
cd ~/ros2_ws
rosdep install -i --from-path src --rosdistro humble -y
  • 우리 패키지를 빌드한다.
cd ~/ros2_ws
colcon build --packages-select cpp_parameters
  • 현재 작업공간의 설치된 executable를 소스하고 실행하여 결과를 확인한다.
cd ~/ros2_ws
source install/setup.bash
ros2 run cpp_parameters minimal_param_node


1초마다 my_parameter로부터 world를 읽어 log를 출려하는 것을 볼 수 있다. 단, log만 터미널에 보여줄 뿐 talker처럼 publish하는 것은 아니니 혼동하지 않도록 하자.


실습 3.1. 터미널에서 parameter 변경하기

자 그러면 아까 timer_callback에서 world로 값을 왜 설정하게 했는지 이유와 관련된 추가 실습을 진행해보자.

터미널에서 node내 파라미터 값을 변경해주자.
먼저 ros2 param list 명령어를 통해 파라미터가 정상적으로 존재하는지 확인한다.

ros2 param list

결과

파라미터도 있겠다 이 값을 이번엔 earth로 변경해보자.
파라미터를 터미널에서 설정할 땐 다음과 같은 명령어를 사용한다. (상단의 파라미터 개념 포스트 및 영상 참고)

ros2 param set /minimal_param_node my_parameter earth

(예시)

그런데 한가지 이상한 것이 earth로 바꼈다가 다시 world로 돌아온 것을 볼 수 있다.

이는 아까 우리가 timer callback에서 사용자가 임의로 파라미터 값을 수정할 것을 대비해 원래 값인 world로 돌려놓도록 코딩했기 때문이다.

이렇게 parameter를 구현하고 코드내에서 해당 파라미터를 이용해서 작업을 할 수 있는 간단한 예시를 보여준다고 보면된다.


3.2. launch파일로 변경하기

저번 launch파일을 작성하는 시간(관련 포스트 및 영상)에서도 배웠지만 다양한 기능을 하는 로봇인 경우엔 node수도 많고 해당 node들의 paramter양도 어마어마하다.

따라서 ros2 param set 명령어로 설정하는건 엄청난 시간이 필요하기 때문에 launch파일을 이용해서 한번에 미리 설정해놓은 값을 계속 사용할 수 있다.

먼저 launch 경로를 패키지 내에 만든다. 경로 내에 cpp_paramters_launch.py를 만든다.

cd ~/ros2_ws/src/cpp_paramters
mkdir launch
cd launch
gedit cpp_parameters_launch.py

다음과 같은 코드를 붙여넣자.

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package="cpp_parameters",
            executable="minimal_param_node",
            name="custom_minimal_param_node",
            output="screen",
            emulate_tty=True,
            parameters=[
                {"my_parameter": "earth"}
            ]
        )
    ])

간단히 코드를 분석해보자

  • cpp_parameters 패키지 안에서 minimal_param_node라는 executable를 실행할 건데 custom_minimal_param_node라는 node이름으로 실행해줘 log 메세지 같은 output은 "screen"(터미널)에 띄어줘
  • emulate_tty=True 는 터미널에 node의 출력을 표시하기 위해 사용된다.
  • parmeters 리스트안에 map 타입으로 my_paramter의 값을 earth로 정의하고 있다.
parameters=[
                {"my_parameter": "earth"}
            ]

이렇게 launch파일을 작성했으면 CMakeLists.txt에 가서 launch 폴더 내용물들이 share폴더에 설치되도록 설정해준다.

install(
  DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)

colcon build하고 실행해준다.

cd ~/ros2_ws
colcon build --packages-select cpp_parameters
source install/setup.bash
ros2 launch cpp_parameters cpp_parameters_launch.py

launch파일에 의해 실행될 때 최초에 node가 생성될 때 earth로 세팅되었다가 timer_callback에서 다시 world로 값이 변경되는 것을 확인 할 수 있다.


요약

오늘은 node내에서 parameter를 정의하고 그 값을 어떻게 획득 및 세팅하는지에 해에 알아보았다.

그 흐름을 정리하자면 다음과 같다.

  • node를 선언할 때 declare_parameter를 사용해 원하는 paramter를 정의한다. 정의할 때 파라미터 이름과 기본값을 설정할 수 있다.
  • get_parameter함수로 node 내 원하는 파라미터 값을 획득할 수 있다.
  • set_parameters함수와 std::vector<rclcpp::Parameter> 타입을 이용해 node내 파라미터들을 변경할 수 있다.

구현 외에 파라미터 설정 방법은 두가지가 있었다.

  • 터미널에서 ros2 param set 명령어를 이용한다.
  • ros2 launch파일을 작성할 때 파라미터 값을 설정해준다.

로봇 개발에서 기능을 구현할 때(node를 만들때) 꼭 필요한 parameter개념이므로 익혀두면 두고두고 도움될 것이다.

문법을 하나하나 외운다기보다는 역시 흐름을 기억하고 항상 참고할 코드를 따로 준비해두도록 하자.

다음 시간에는 방대한 데이터를 처리해야하는 로봇개발에서 유용하게 사용될 수 있는 component(composable node)에 대한 기초 개념을 배워볼 예정이다.


질문하고 싶거나 인공지능 & 로봇 개발에 대해 다뤄줬으면 하는 주제를 댓글로 남겨주기 바란다~!

오픈톡 문의: https://open.kakao.com/o/sXMqcQAf

IRoboU 유튜브 채널

참고 문헌

profile
지식이 현실이 되는 공간

2개의 댓글

comment-user-thumbnail
2024년 2월 25일

ROS2를 독학하고 있는데 있어, 너무 큰 도움이 됩니다. 감사합니다.

1개의 답글