본문으로 바로가기

ROS 2 C++ 파라미터 실습

category 강좌/ROS2 2026. 5. 15. 13:35

ROS 2를 사용하다 보면 같은 프로그램이라도 상황에 따라 설정값을 바꿔야 하는 경우가 자주 생깁니다.

예를 들어 모바일 로봇의 주행 속도, 제어 주기, 장애물 감지 거리, 좌표계 이름, 제어 게인 같은 값들은 매번 코드를 수정해서 다시 빌드하는 방식으로 관리하면 매우 비효율적입니다.

이때 사용하는 것이 파라미터(parameter) 입니다.

 

ROS 2에서 파라미터는 쉽게 말해 노드가 사용하는 설정값입니다. 정수, 실수, 문자열, 불리언, 리스트 같은 값을 저장할 수 있습니다.

 

모바일 로봇 개발에서는 파라미터를 잘 쓰는 것이 중요합니다. 코드 안에 모든 값을 고정해 두면 실험할 때마다 컴파일을 반복해야 합니다. 반대로 파라미터를 사용하면 launch 파일이나 YAML 파일, CLI 명령어를 통해 같은 노드를 다양한 조건으로 실행할 수 있습니다.

 

이번 실습 환경은 다음과 같습니다.

 
ROS 2 배포판: humble
워크스페이스: ~/ros2_study
패키지 이름: my_cpp_package
 

 

패키지는 이미 생성되어 있다고 가정합니다.

 

 

 

 

1. 파라미터가 필요한 이유

초보 단계에서는 코드 안에 값을 직접 넣는 방식이 편해 보일 수 있습니다.

 
double max_speed = 1.0;
double control_period = 0.1;
int qos_depth = 10;
 

하지만 실제 모바일 로봇을 운용하다 보면 다음과 같은 문제가 생깁니다.

 

첫째, 환경이 바뀔 때마다 코드를 수정해야 합니다.

둘째, 수정 후 다시 빌드해야 하므로 테스트 속도가 느려집니다.

셋째, 여러 로봇에 같은 프로그램을 배포할 때 로봇별 설정을 관리하기 어렵습니다.

넷째, 실험 조건을 기록하거나 재현하기 어렵습니다.

 

파라미터를 사용하면 위 문제를 상당 부분 줄일 수 있습니다.

 

예를 들어 실내 주행 로봇에서는 속도를 낮게 설정하고, 넓은 공간에서는 속도를 높게 설정할 수 있습니다. 코드 자체는 그대로 두고 설정 파일만 바꾸면 됩니다.

 
mobile_base_controller:
  ros__parameters:
    max_linear_velocity: 0.5
    max_angular_velocity: 1.0
    obstacle_stop_distance: 0.8
    control_frequency: 50.0
 

 

이 방식은 로봇 소프트웨어를 코드와 설정으로 분리하는 기본 습관입니다.

 

 

 

 

2. ROS 2 파라미터의 기본 개념

ROS 2에서 파라미터는 특정 노드에 소속됩니다.

즉, 파라미터는 전역 변수처럼 아무 곳에서나 막 쓰는 값이 아닙니다. 기본적으로는 어떤 노드의 어떤 파라미터인가가 중요합니다.

 

예를 들어 다음과 같은 파라미터가 있다고 하겠습니다.

 
/mobile_base_controller/max_linear_velocity
/mobile_base_controller/max_angular_velocity
/mobile_base_controller/obstacle_stop_distance
/mobile_base_controller/control_frequency
 

 

여기서 max_linear_velocity는 mobile_base_controller 노드의 파라미터입니다.

이 구조 덕분에 여러 노드가 같은 이름의 파라미터를 사용하더라도 충돌을 줄일 수 있습니다.

 

 

 

 

3. 파라미터로 관리하기 좋은 값

모든 값을 파라미터로 만들 필요는 없습니다.

 

파라미터로 관리하면 좋은 값은 보통 다음과 같습니다.

                                   종류                                                                    예시
실험 중 자주 바뀌는 값 속도 제한, PID 게인, 필터 계수
환경에 따라 달라지는 값 지도 파일 경로, 센서 위치, 좌표계 이름
실행 옵션 디버그 모드, 시뮬레이션 모드
통신 설정 QoS depth, 주기
임계값 장애물 거리 기준, 배터리 경고 기준

 

반대로 코드의 핵심 알고리즘 자체, 메시지 타입, 클래스 구조처럼 프로그램의 뼈대가 되는 부분은 파라미터보다 코드로 명확하게 관리하는 편이 좋습니다.

 

 

 

 

4. CLI로 파라미터 확인하기

ROS 2에서는 ros2 param 명령어로 실행 중인 노드의 파라미터를 확인하거나 변경할 수 있습니다.

 

예를 들어 실행 중인 노드 중 /mobile_base_controller라는 노드가 있다고 가정하겠습니다.

 

파라미터 목록 확인:

 
ros2 param list
 

 

특정 노드의 파라미터 확인:

 
ros2 param get /mobile_base_controller max_linear_velocity
 

 

파라미터 값 변경:

 
ros2 param set /mobile_base_controller max_linear_velocity 0.8
 

 

이 방식은 실습할 때 매우 유용합니다. 특히 모바일 로봇을 직접 움직이면서 속도, 거리 기준, 제어 게인을 조금씩 조정할 때 CLI 파라미터 변경은 빠른 튜닝 도구가 됩니다.

 

다만 주의할 점이 있습니다.

노드 코드에서 파라미터 변경을 허용하지 않거나, 타입이 맞지 않으면 변경이 실패할 수 있습니다.

 

예를 들어 double로 선언된 파라미터에 문자열을 넣으려고 하면 문제가 발생할 수 있습니다. 이는 실수로 잘못된 타입의 값을 넣는 오류를 막기 위한 안전장치입니다.

 

 

 

 

5. C++ 노드에서 파라미터 선언하기

ROS 2 C++에서는 보통 rclcpp::Node를 상속한 클래스 안에서 파라미터를 선언하고 읽습니다.

 

가장 기본적인 흐름은 다음과 같습니다.

 
this->declare_parameter<double>("max_linear_velocity", 0.6);
this->declare_parameter<double>("max_angular_velocity", 1.2);
this->declare_parameter<double>("obstacle_stop_distance", 0.5);
this->declare_parameter<double>("control_frequency", 50.0);
this->declare_parameter<std::string>("base_frame", "base_link");
 

 

선언한 파라미터는 다음처럼 읽을 수 있습니다.

 
double max_linear_velocity = this->get_parameter("max_linear_velocity").as_double();
double max_angular_velocity = this->get_parameter("max_angular_velocity").as_double();
double obstacle_stop_distance = this->get_parameter("obstacle_stop_distance").as_double();
double control_frequency = this->get_parameter("control_frequency").as_double();
std::string base_frame = this->get_parameter("base_frame").as_string();
 
 
 
 
 

6. C++ 예제 코드 작성하기

기존 패키지 my_cpp_package 안에 다음 파일을 작성합니다.

 
cd ~/ros2_study/src/my_cpp_package
mkdir -p src
 

 

파일 생성:

 
touch src/mobile_base_controller.cpp
 

 

 

 

아래 코드를 작성합니다.

 
#include <rclcpp/rclcpp.hpp>

class MobileBaseController : public rclcpp::Node
{
public:
  MobileBaseController()
  : Node("mobile_base_controller")
  {
    this->declare_parameter<double>("max_linear_velocity", 0.6);
    this->declare_parameter<double>("max_angular_velocity", 1.2);
    this->declare_parameter<double>("obstacle_stop_distance", 0.5);
    this->declare_parameter<double>("control_frequency", 50.0);
    this->declare_parameter<std::string>("base_frame", "base_link");
    this->declare_parameter<std::string>("odom_frame", "odom");

    max_linear_velocity_ =
      this->get_parameter("max_linear_velocity").as_double();

    max_angular_velocity_ =
      this->get_parameter("max_angular_velocity").as_double();

    obstacle_stop_distance_ =
      this->get_parameter("obstacle_stop_distance").as_double();

    control_frequency_ =
      this->get_parameter("control_frequency").as_double();

    base_frame_ =
      this->get_parameter("base_frame").as_string();

    odom_frame_ =
      this->get_parameter("odom_frame").as_string();

    RCLCPP_INFO(this->get_logger(), "max_linear_velocity: %.2f", max_linear_velocity_);
    RCLCPP_INFO(this->get_logger(), "max_angular_velocity: %.2f", max_angular_velocity_);
    RCLCPP_INFO(this->get_logger(), "obstacle_stop_distance: %.2f", obstacle_stop_distance_);
    RCLCPP_INFO(this->get_logger(), "control_frequency: %.2f", control_frequency_);
    RCLCPP_INFO(this->get_logger(), "base_frame: %s", base_frame_.c_str());
    RCLCPP_INFO(this->get_logger(), "odom_frame: %s", odom_frame_.c_str());
  }

private:
  double max_linear_velocity_;
  double max_angular_velocity_;
  double obstacle_stop_distance_;
  double control_frequency_;
  std::string base_frame_;
  std::string odom_frame_;
};

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

 

 

 

이 예제의 핵심은 명확합니다.

파라미터를 먼저 선언하고, 그 값을 읽어서 노드 내부 변수에 저장합니다.

이후 모바일 로봇의 주행 제어, 속도 제한, 장애물 정지 판단, 좌표계 처리 등에 이 값을 사용할 수 있습니다.

 

 

 

7. 소스 코드 분석

이 코드는 MobileBaseController라는 ROS 2 노드를 생성합니다.

 
class MobileBaseController : public rclcpp::Node
 

 

MobileBaseController 클래스는 rclcpp::Node를 상속합니다.
즉, 이 클래스 자체가 하나의 ROS 2 노드 역할을 합니다.

 

노드 이름은 생성자에서 다음과 같이 지정합니다.

 
: Node("mobile_base_controller")
 

 

따라서 실행 중인 노드 이름은 다음과 같습니다.

 

 

/mobile_base_controller
 

 

 

 

 

1) 파라미터 선언

생성자 안에서는 모바일 로봇 제어에 필요한 여러 설정값을 파라미터로 선언합니다.

 
this->declare_parameter<double>("max_linear_velocity", 0.6);
this->declare_parameter<double>("max_angular_velocity", 1.2);
this->declare_parameter<double>("obstacle_stop_distance", 0.5);
this->declare_parameter<double>("control_frequency", 50.0);
this->declare_parameter<std::string>("base_frame", "base_link");
this->declare_parameter<std::string>("odom_frame", "odom");
 

 

각 파라미터의 의미는 다음과 같습니다.

                      파라미터                                       기본값                                    의미
max_linear_velocity 0.6 모바일 로봇의 최대 직진 속도
max_angular_velocity 1.2 모바일 로봇의 최대 회전 속도
obstacle_stop_distance 0.5 장애물을 감지했을 때 정지할 기준 거리
control_frequency 50.0 제어 루프 실행 주기
base_frame "base_link" 로봇 본체 기준 좌표계 이름
odom_frame "odom" 오도메트리 기준 좌표계 이름

 

여기서 중요한 점은 코드 안에 기본값을 넣어 두었다는 것입니다.
따라서 YAML 파일이나 launch 파일에서 별도 값을 전달하지 않아도 노드는 기본값으로 실행됩니다.

 

 

 

2) 파라미터 값 읽기

선언한 파라미터는 get_parameter()를 사용해서 읽습니다.

 
max_linear_velocity_ =
  this->get_parameter("max_linear_velocity").as_double();
 

 

이 코드는 max_linear_velocity 파라미터 값을 읽어서 클래스 내부 변수인 max_linear_velocity_에 저장합니다.

 

나머지 파라미터도 같은 방식으로 읽습니다.

 
max_angular_velocity_ =
  this->get_parameter("max_angular_velocity").as_double();

obstacle_stop_distance_ =
  this->get_parameter("obstacle_stop_distance").as_double();

control_frequency_ =
  this->get_parameter("control_frequency").as_double();
 

 

문자열 파라미터는 as_string()으로 읽습니다.

 
base_frame_ =
  this->get_parameter("base_frame").as_string();

odom_frame_ =
  this->get_parameter("odom_frame").as_string();
 

 

즉, 숫자형 파라미터는 as_double(), 문자열 파라미터는 as_string()을 사용합니다.

 

 

 

3) 읽어온 값 출력

파라미터 값을 정상적으로 읽었는지 확인하기 위해 로그를 출력합니다.

 
RCLCPP_INFO(this->get_logger(), "max_linear_velocity: %.2f", max_linear_velocity_);
 

 

RCLCPP_INFO()는 ROS 2에서 정보를 터미널에 출력할 때 사용하는 로그 함수입니다.

 

실행하면 다음과 비슷한 출력이 나옵니다.

 
[INFO] [mobile_base_controller]: max_linear_velocity: 0.60
[INFO] [mobile_base_controller]: max_angular_velocity: 1.20
[INFO] [mobile_base_controller]: obstacle_stop_distance: 0.50
[INFO] [mobile_base_controller]: control_frequency: 50.00
[INFO] [mobile_base_controller]: base_frame: base_link
[INFO] [mobile_base_controller]: odom_frame: odom
 

 

이 출력은 파라미터가 정상적으로 선언되고 읽혔는지 확인하는 용도로 사용합니다.

 

 

 

4) private 변수 역할

클래스 내부에는 다음과 같은 멤버 변수가 있습니다.

 
double max_linear_velocity_;
double max_angular_velocity_;
double obstacle_stop_distance_;
double control_frequency_;
std::string base_frame_;
std::string odom_frame_;
 

 

이 변수들은 파라미터 값을 노드 내부에서 사용하기 위해 저장하는 공간입니다.

 

예를 들어 실제 모바일 로봇 제어 코드가 추가된다면 다음과 같은 용도로 사용할 수 있습니다.

 
if (target_speed > max_linear_velocity_) {
  target_speed = max_linear_velocity_;
}
 

 

즉, 파라미터는 외부에서 설정하는 값이고, 멤버 변수는 그 값을 코드 내부에서 사용하기 위한 저장 공간입니다.

 

 

 

5) main 함수 설명

 

마지막 부분은 ROS 2 노드를 실행하는 기본 구조입니다.

 
rclcpp::init(argc, argv);
 

 

ROS 2 시스템을 초기화합니다.

 
rclcpp::spin(std::make_shared<MobileBaseController>());
 

 

MobileBaseController 노드를 생성하고 실행 상태로 유지합니다.

 
rclcpp::shutdown();
 

 

노드 실행이 끝나면 ROS 2 시스템을 종료합니다.

 

 

 

 

8. CMakeLists.txt 수정하기

my_cpp_package의 CMakeLists.txt에 실행 파일을 추가합니다.

 
add_executable(mobile_base_controller src/mobile_base_controller.cpp)
ament_target_dependencies(mobile_base_controller rclcpp)

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

launch 파일과 config 파일을 사용할 것이므로 아래 설치 규칙도 추가합니다.

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

 

이 부분을 빼먹으면 빌드 후 install 공간에서 launch 파일이나 config 파일을 찾지 못하는 문제가 생길 수 있습니다.

 

 

 

 

 

9. YAML 파일로 파라미터 관리하기

실제 프로젝트에서는 파라미터를 코드에 직접 넣기보다 YAML 파일에 정리하는 경우가 많습니다.

 

패키지 안에 config 디렉터리를 만듭니다. 기존에 config 폴더가 있으면 새롭게 생성하지 않아도 됩니다.

 
cd ~/ros2_study/src/my_cpp_package
mkdir -p config
 

 

 

파일 생성:

 
touch config/mobile_base_controller.yaml
 

 

 

 

아래 내용을 작성합니다.

 
mobile_base_controller:
  ros__parameters:
    max_linear_velocity: 0.7
    max_angular_velocity: 1.5
    obstacle_stop_distance: 0.6
    control_frequency: 50.0
    base_frame: "base_link"
    odom_frame: "odom"
 

 

 

 

여기서 중요한 부분은 ros__parameters입니다.

 

이 아래에 작성한 값들이 ROS 2 파라미터로 적용됩니다.

또 하나 중요한 점은 노드 이름과 YAML의 최상위 이름이 맞아야 한다는 것입니다.

 

이번 예제의 노드 이름은 다음과 같습니다.

 
Node("mobile_base_controller")
 

 

따라서 YAML 파일에서도 다음처럼 작성해야 합니다.

 
mobile_base_controller:
  ros__parameters:
 

 

이름이 다르면 YAML 파라미터가 적용되지 않을 수 있습니다.

 

 

 

 

10. Launch 파일에서 YAML 파라미터 적용하기

패키지 안에 launch 디렉터리를 만듭니다.

 
cd ~/ros2_study/src/my_cpp_package
mkdir -p launch
 

 

 

파일 생성:

 
touch launch/mobile_base_controller.launch.py
 

 

 

아래 내용을 작성합니다.

 
import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    config = os.path.join(
        get_package_share_directory('my_cpp_package'),
        'config',
        'mobile_base_controller.yaml'
    )

    return LaunchDescription([
        Node(
            package='my_cpp_package',
            executable='mobile_base_controller',
            name='mobile_base_controller',
            output='screen',
            parameters=[config]
        )
    ])
 

 

 

 

이렇게 하면 mobile_base_controller 노드가 실행될 때 mobile_base_controller.yaml에 있는 파라미터가 자동으로 적용됩니다.

 

ROS 2에서는 빌드 후 install 공간에 launch 파일이 설치되어야 합니다.

 

따라서 CMakeLists.txt 파일을 수정합니다.

 

아래 내용을 추가합니다.

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

 

전체 구조는 대략 다음과 같아야 합니다.

 
cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(mobile_base_controller src/mobile_base_controller.cpp)
ament_target_dependencies(mobile_base_controller rclcpp)

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

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

ament_package()
 

 

여기서 중요한 부분은 두 가지입니다.

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

 

이 부분은 C++ 실행 파일을 설치합니다.

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

 

이 부분은 launch 디렉터리를 설치합니다.

둘 중 하나라도 빠지면 ros2 launch 실행 시 문제가 생길 수 있습니다.

 

 

 

 

 

11. 빌드하기

 

워크스페이스로 이동합니다.

 
cd ~/ros2_study
 

 

빌드합니다.

 
colcon build --packages-select my_cpp_package
 

 

환경 설정을 적용합니다.

 
source install/setup.bash
 

 

 

 

 

 

 

12. 실행하기

 

launch 파일로 실행합니다.

 
ros2 launch my_cpp_package mobile_base_controller.launch.py
 

 

정상적으로 실행되면 다음과 비슷한 로그가 출력됩니다.

 
[INFO] [mobile_base_controller]: max_linear_velocity: 0.70
[INFO] [mobile_base_controller]: max_angular_velocity: 1.50
[INFO] [mobile_base_controller]: obstacle_stop_distance: 0.60
[INFO] [mobile_base_controller]: control_frequency: 50.00
[INFO] [mobile_base_controller]: base_frame: base_link
[INFO] [mobile_base_controller]: odom_frame: odom
 

 

여기서 중요한 점은 코드에 작성된 기본값이 아니라 YAML 파일에 작성한 값이 적용된다는 것입니다.

 

예를 들어 코드의 기본값은 다음과 같습니다.

 
max_linear_velocity = 0.6
 

 

하지만 YAML 파일에는 다음처럼 작성했습니다.

 
max_linear_velocity: 0.7
 

 

실행 결과에서 0.70이 출력된다면 YAML 파라미터가 정상 적용된 것입니다.

 

 

 

 

 

13. 실행 중 파라미터 변경하기

파라미터의 장점은 실행 전에만 설정하는 것이 아니라, 실행 중에도 값을 바꿀 수 있다는 점입니다.

 

노드를 실행한 상태에서 새 터미널을 열고 다음 명령어를 입력합니다.

 
source ~/ros2_study/install/setup.bash
 

 

파라미터 목록을 확인합니다.

 
ros2 param list
 
 

 

특정 파라미터 값을 확인합니다.

 
ros2 param get /mobile_base_controller max_linear_velocity
 

 

 

값을 변경합니다.

 
ros2 param set /mobile_base_controller max_linear_velocity 0.3
 

 

이 명령은 /mobile_base_controller 노드의 max_linear_velocity 값을 0.3으로 바꿉니다.

하지만 여기서 한 가지를 구분해야 합니다.

 

파라미터 값이 바뀌는 것과 노드 내부 변수에 즉시 반영되는 것은 별개의 문제입니다.

 

예를 들어 생성자에서 한 번만 값을 읽어 max_linear_velocity_ 변수에 저장했다면, CLI로 파라미터를 바꿔도 max_linear_velocity_ 값은 자동으로 바뀌지 않을 수 있습니다.

따라서 실행 중 변경을 실제 로직에 반영하려면 파라미터 변경 콜백을 등록해야 합니다.

 

 

 

 

 

14. 파라미터 변경 콜백 사용하기

실행 중 파라미터 변경을 노드 내부 변수에 반영하려면 add_on_set_parameters_callback()을 사용합니다.

src/mobile_base_controller.cpp를 다음과 같이 수정합니다.

 
#include <rclcpp/rclcpp.hpp>
#include <rcl_interfaces/msg/set_parameters_result.hpp>

class MobileBaseController : public rclcpp::Node
{
public:
  MobileBaseController()
  : Node("mobile_base_controller")
  {
    this->declare_parameter<double>("max_linear_velocity", 0.6);
    this->declare_parameter<double>("max_angular_velocity", 1.2);
    this->declare_parameter<double>("obstacle_stop_distance", 0.5);
    this->declare_parameter<double>("control_frequency", 50.0);
    this->declare_parameter<std::string>("base_frame", "base_link");
    this->declare_parameter<std::string>("odom_frame", "odom");

    max_linear_velocity_ =
      this->get_parameter("max_linear_velocity").as_double();

    max_angular_velocity_ =
      this->get_parameter("max_angular_velocity").as_double();

    obstacle_stop_distance_ =
      this->get_parameter("obstacle_stop_distance").as_double();

    control_frequency_ =
      this->get_parameter("control_frequency").as_double();

    base_frame_ =
      this->get_parameter("base_frame").as_string();

    odom_frame_ =
      this->get_parameter("odom_frame").as_string();

    parameter_callback_handle_ =
      this->add_on_set_parameters_callback(
        std::bind(
          &MobileBaseController::onParameterChanged,
          this,
          std::placeholders::_1
        )
      );

    printParameters();
  }

private:
  void printParameters()
  {
    RCLCPP_INFO(this->get_logger(), "max_linear_velocity: %.2f", max_linear_velocity_);
    RCLCPP_INFO(this->get_logger(), "max_angular_velocity: %.2f", max_angular_velocity_);
    RCLCPP_INFO(this->get_logger(), "obstacle_stop_distance: %.2f", obstacle_stop_distance_);
    RCLCPP_INFO(this->get_logger(), "control_frequency: %.2f", control_frequency_);
    RCLCPP_INFO(this->get_logger(), "base_frame: %s", base_frame_.c_str());
    RCLCPP_INFO(this->get_logger(), "odom_frame: %s", odom_frame_.c_str());
  }

  rcl_interfaces::msg::SetParametersResult onParameterChanged(
    const std::vector<rclcpp::Parameter> & parameters)
  {
    rcl_interfaces::msg::SetParametersResult result;
    result.successful = true;

    for (const auto & param : parameters) {
      if (param.get_name() == "max_linear_velocity") {
        double value = param.as_double();

        if (value < 0.0) {
          result.successful = false;
          result.reason = "max_linear_velocity must be greater than or equal to 0.0";
          return result;
        }

        max_linear_velocity_ = value;
        RCLCPP_INFO(
          this->get_logger(),
          "Updated max_linear_velocity: %.2f",
          max_linear_velocity_
        );
      }

      if (param.get_name() == "max_angular_velocity") {
        double value = param.as_double();

        if (value < 0.0) {
          result.successful = false;
          result.reason = "max_angular_velocity must be greater than or equal to 0.0";
          return result;
        }

        max_angular_velocity_ = value;
        RCLCPP_INFO(
          this->get_logger(),
          "Updated max_angular_velocity: %.2f",
          max_angular_velocity_
        );
      }

      if (param.get_name() == "obstacle_stop_distance") {
        double value = param.as_double();

        if (value < 0.0) {
          result.successful = false;
          result.reason = "obstacle_stop_distance must be greater than or equal to 0.0";
          return result;
        }

        obstacle_stop_distance_ = value;
        RCLCPP_INFO(
          this->get_logger(),
          "Updated obstacle_stop_distance: %.2f",
          obstacle_stop_distance_
        );
      }

      if (param.get_name() == "control_frequency") {
        double value = param.as_double();

        if (value <= 0.0) {
          result.successful = false;
          result.reason = "control_frequency must be greater than 0.0";
          return result;
        }

        control_frequency_ = value;
        RCLCPP_INFO(
          this->get_logger(),
          "Updated control_frequency: %.2f",
          control_frequency_
        );
      }
    }

    return result;
  }

  double max_linear_velocity_;
  double max_angular_velocity_;
  double obstacle_stop_distance_;
  double control_frequency_;
  std::string base_frame_;
  std::string odom_frame_;

  OnSetParametersCallbackHandle::SharedPtr parameter_callback_handle_;
};

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

 

 

 

1) 추가된 헤더 파일

 
#include <rcl_interfaces/msg/set_parameters_result.hpp>
 

 

이 헤더 파일은 파라미터 변경 결과를 반환하기 위해 필요합니다.

파라미터 변경 콜백 함수는 변경 요청을 받은 뒤, 그 요청을 허용할지 거부할지 결과를 돌려줘야 합니다.

 

그때 사용하는 메시지 타입이 다음입니다.

 
rcl_interfaces::msg::SetParametersResult
 

 

예를 들어 파라미터 값이 정상이라면 변경을 허용합니다.

 
result.successful = true;
 

 

반대로 값이 잘못되었다면 변경을 거부합니다.

 
result.successful = false;
result.reason = "max_linear_velocity must be greater than or equal to 0.0";
 

 

즉, 이 헤더는 파라미터 변경 성공 또는 실패 결과를 표현하기 위해 추가된 것입니다.

 

 

 

2) 파라미터 변경 콜백 등록

 

다음 코드는 파라미터 변경 콜백을 등록하는 부분입니다.

 
parameter_callback_handle_ =
  this->add_on_set_parameters_callback(
    std::bind(
      &MobileBaseController::onParameterChanged,
      this,
      std::placeholders::_1
    )
  );
 

 

이 코드는 쉽게 말해 다음 의미입니다.

이 노드의 파라미터가 변경되면
onParameterChanged() 함수를 자동으로 호출해라.
 

 

즉, 사용자가 실행 중에 다음과 같은 명령어를 입력하면,

 
ros2 param set /mobile_base_controller max_linear_velocity 0.4
 

 

ROS 2는 내부적으로 onParameterChanged() 함수를 호출합니다.

그리고 변경하려는 파라미터 정보가 함수로 전달됩니다.

 

 

 

3) std::bind의 역할

 

콜백 등록 코드 안에는 std::bind()가 사용됩니다.

 
std::bind(
  &MobileBaseController::onParameterChanged,
  this,
  std::placeholders::_1
)
 

 

이 부분은 클래스 안에 있는 멤버 함수인 onParameterChanged()를 콜백 함수로 연결하기 위한 코드입니다.

일반 함수라면 함수 이름만 넘길 수 있지만, onParameterChanged()는 MobileBaseController 클래스 안에 있는 멤버 함수입니다.

 

그래서 다음 정보가 필요합니다.

어떤 클래스의 함수인지
어떤 객체에서 실행할 것인지
외부에서 들어오는 인자를 어디에 넣을 것인지
 

 

각 부분의 의미는 다음과 같습니다.

                                          코드                                                               의미
&MobileBaseController::onParameterChanged 콜백으로 사용할 멤버 함수
this 현재 생성된 노드 객체
std::placeholders::_1 ROS 2가 전달하는 파라미터 목록 인자

 

즉, 이 코드는 현재 객체의 onParameterChanged() 함수를 파라미터 변경 콜백으로 연결하는 코드입니다.

 

 

 

4) printParameters() 호출

 
printParameters();
 

 

이 함수는 현재 파라미터 값을 출력하기 위해 호출됩니다.

파라미터를 선언하고 값을 읽은 뒤, 현재 노드가 어떤 설정값으로 실행되고 있는지 터미널에 보여줍니다.

 
void printParameters()
{
  RCLCPP_INFO(this->get_logger(), "max_linear_velocity: %.2f", max_linear_velocity_);
  RCLCPP_INFO(this->get_logger(), "max_angular_velocity: %.2f", max_angular_velocity_);
  RCLCPP_INFO(this->get_logger(), "obstacle_stop_distance: %.2f", obstacle_stop_distance_);
  RCLCPP_INFO(this->get_logger(), "control_frequency: %.2f", control_frequency_);
  RCLCPP_INFO(this->get_logger(), "base_frame: %s", base_frame_.c_str());
  RCLCPP_INFO(this->get_logger(), "odom_frame: %s", odom_frame_.c_str());
}
 

 

이 함수는 콜백과 직접 관련된 핵심 기능은 아니지만, 코드가 길어지는 것을 막고 출력 기능을 따로 정리하기 위해 추가한 함수입니다.

기존에는 생성자 안에서 바로 RCLCPP_INFO()를 여러 번 호출했습니다.

수정 후에는 출력 부분을 printParameters() 함수로 분리했습니다.

그래서 코드가 더 깔끔해집니다.

 

 

 

5) 파라미터 변경 콜백 함수

다음 함수가 실제로 파라미터 변경을 처리하는 함수입니다.

 
rcl_interfaces::msg::SetParametersResult onParameterChanged(
  const std::vector<rclcpp::Parameter> & parameters)
{
}
 

 

이 함수는 파라미터 변경 요청이 들어올 때 자동으로 호출됩니다.

인자로 들어오는 parameters는 변경 요청이 들어온 파라미터 목록입니다.

 
const std::vector<rclcpp::Parameter> & parameters
 

 

하나의 파라미터만 바뀔 수도 있고, 여러 개가 한 번에 바뀔 수도 있기 때문에 vector 형태로 받습니다.

그래서 함수 안에서는 반복문으로 하나씩 확인합니다.

 
for (const auto & param : parameters) {
}
 

 

 

 

6) 변경할 파라미터 이름 확인

콜백 함수 안에서는 먼저 어떤 파라미터가 변경되었는지 확인합니다.

 
if (param.get_name() == "max_linear_velocity") {
}
 

 

이 코드는 변경 요청이 들어온 파라미터 이름이 max_linear_velocity인지 확인합니다.

맞다면 값을 읽습니다.

 
double value = param.as_double();
 

 

그리고 값이 정상 범위인지 검사합니다.

 
if (value < 0.0) {
  result.successful = false;
  result.reason = "max_linear_velocity must be greater than or equal to 0.0";
  return result;
}
 

 

속도 값이 음수이면 모바일 로봇 제어 기준으로 잘못된 값입니다.

그래서 변경을 거부합니다.

 

정상 값이면 내부 변수에 반영합니다.

 
max_linear_velocity_ = value;
 

 

그리고 변경된 값을 로그로 출력합니다.

 
RCLCPP_INFO(
  this->get_logger(),
  "Updated max_linear_velocity: %.2f",
  max_linear_velocity_
);
 
 
 
 
 

7) SetParametersResult의 역할

콜백 함수는 마지막에 반드시 결과를 반환해야 합니다.

 
return result;
 

 

result.successful 값이 true이면 파라미터 변경을 허용합니다.

 
result.successful = true;
 

 

false이면 파라미터 변경을 거부합니다.

 
result.successful = false;
 

 

예를 들어 다음 명령어는 정상입니다.

 
ros2 param set /mobile_base_controller max_linear_velocity 0.4
 

 

이 경우 콜백 함수는 변경을 허용하고, 내부 변수도 0.4로 바뀝니다.

 

반대로 다음 명령어는 잘못된 값입니다.

 
ros2 param set /mobile_base_controller max_linear_velocity -1.0
 

 

이 경우 콜백 함수는 변경을 거부합니다.

이 구조 덕분에 위험한 설정값이 로봇 제어 코드에 적용되는 것을 막을 수 있습니다.

 

 

 

8) 콜백 핸들 변수

다음 변수도 새롭게 추가되었습니다.

 
OnSetParametersCallbackHandle::SharedPtr parameter_callback_handle_;
 

 

이 변수는 등록한 파라미터 콜백을 유지하기 위한 핸들입니다.

ROS 2에서는 add_on_set_parameters_callback()으로 콜백을 등록하면 콜백 핸들이 반환됩니다.

이 핸들을 클래스 멤버 변수로 저장해 두어야 콜백이 계속 유지됩니다.

 
parameter_callback_handle_ =
  this->add_on_set_parameters_callback(...);
 

 

만약 이 핸들을 저장하지 않으면 콜백이 제대로 유지되지 않을 수 있습니다.

따라서 파라미터 변경 콜백을 사용할 때는 보통 클래스 멤버 변수로 선언해 둡니다.

 

 

 

 

 

15. 다시 빌드하고 테스트하기

코드를 수정했으므로 다시 빌드합니다.

 
cd ~/ros2_study
colcon build --packages-select my_cpp_package
source install/setup.bash
 

 

 

launch 파일로 실행합니다.

 
ros2 launch my_cpp_package mobile_base_controller.launch.py
 

 

 

새 터미널에서 파라미터를 변경합니다.

 
source ~/ros2_study/install/setup.bash
ros2 param set /mobile_base_controller max_linear_velocity 0.4
 

 

정상적으로 반영되면 실행 중인 터미널에 다음과 비슷한 로그가 출력됩니다.

 
[INFO] [mobile_base_controller]: Updated max_linear_velocity: 0.40
 

 

 

 

잘못된 값을 넣어 봅니다.

 
ros2 param set /mobile_base_controller max_linear_velocity -1.0
 

 

 

이 경우 변경이 거부되어야 합니다.

이런 검증 로직은 실제 모바일 로봇에서 매우 중요합니다. 속도, 가속도, 회전 속도, 장애물 정지 거리 같은 값은 잘못 설정하면 로봇이 위험하게 움직일 수 있기 때문입니다.

 

 

 

 

16. 모바일 로봇 제어 노드 파라미터 설계 예시

모바일 로봇 제어 노드가 있다고 가정하겠습니다.

이 노드는 최대 직진 속도, 최대 회전 속도, 장애물 감지 거리, 제어 주기, 기준 좌표계를 파라미터로 가질 수 있습니다.

 
mobile_base_controller:
  ros__parameters:
    max_linear_velocity: 0.6
    max_angular_velocity: 1.2
    obstacle_stop_distance: 0.5
    control_frequency: 50.0
    base_frame: "base_link"
    odom_frame: "odom"
 

 

각 파라미터의 의미는 다음과 같습니다.

                                    파라미터                                               의미
max_linear_velocity 직진 최대 속도
max_angular_velocity 회전 최대 속도
obstacle_stop_distance 장애물 정지 기준 거리
control_frequency 제어 루프 주기
base_frame 로봇 기준 좌표계
odom_frame 오도메트리 좌표계

 

이런 식으로 파라미터 이름을 명확하게 작성하면 나중에 다른 개발자가 봐도 이해하기 쉽습니다.

좋은 파라미터 이름은 코드 주석보다 강력할 때가 많습니다.

 

 

 

 

17. 파라미터 작성 시 주의할 점

ROS 2 파라미터를 사용할 때 자주 발생하는 실수는 다음과 같습니다.

 

첫째, 코드에서 선언하지 않은 파라미터를 YAML에만 작성하는 경우입니다.

노드 설정에 따라 선언되지 않은 파라미터가 무시되거나 오류가 날 수 있습니다.

 

둘째, YAML의 노드 이름과 실제 실행 노드 이름이 다른 경우입니다.

 

예를 들어 YAML은 다음과 같은데,

 
controller_node:
  ros__parameters:
    max_linear_velocity: 1.0
 

 

launch 파일에서 노드 이름을 이렇게 실행하면,

 
Node(
    package='my_cpp_package',
    executable='mobile_base_controller',
    name='mobile_base_controller',
    parameters=[config]
)
 

 

실제 노드 이름은 mobile_base_controller입니다.

따라서 YAML의 controller_node 아래 값이 적용되지 않을 수 있습니다.

 

올바른 작성은 다음과 같습니다.

 
mobile_base_controller:
  ros__parameters:
    max_linear_velocity: 1.0
 

 

셋째, 타입을 잘못 쓰는 경우입니다.

 
max_linear_velocity: "1.0"
 

 

이렇게 작성하면 숫자 1.0이 아니라 문자열 "1.0"이 됩니다.

C++ 코드에서 double로 읽으려고 하면 문제가 생길 수 있습니다.

 

올바른 작성은 다음과 같습니다.

 
max_linear_velocity: 1.0
 

 

넷째, 빌드 후 config 파일 설치를 빼먹는 경우입니다.

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

 

이 설정을 하지 않으면 ros2 launch 실행 시 파일 경로를 찾지 못할 수 있습니다.

 

 

 

 

18. 좋은 파라미터 설계 원칙

파라미터는 많다고 좋은 것이 아닙니다.

너무 많은 파라미터는 오히려 유지보수를 어렵게 만듭니다.

 

다음 기준을 추천드립니다.

 

첫째, 이름만 보고 의미를 알 수 있게 작성하십시오.

나쁜 예:

 
v: 0.5
d: 1.0
flag: true
 

 

좋은 예:

 
max_linear_velocity: 0.5
obstacle_stop_distance: 1.0
enable_debug_log: true
 

 

둘째, 단위를 이름이나 주석으로 명확히 하십시오.

 
control_period_sec: 0.02
max_speed_mps: 0.6
turn_rate_radps: 1.0
 

 

셋째, 안전과 관련된 값은 콜백에서 검증하십시오.

속도, 가속도, 거리, 전류 제한 같은 값은 잘못 설정하면 실제 장비에 영향을 줄 수 있습니다.

 

넷째, 기본값을 반드시 코드에 넣으십시오.

YAML 파일이 없더라도 노드가 최소한의 기본값으로 실행되도록 만드는 것이 좋습니다.

 

다섯째, 실험용 파라미터와 운영용 파라미터를 구분하십시오.

 

예를 들어 다음처럼 분리할 수 있습니다.

 
config/dev.yaml
config/real_robot.yaml
config/simulation.yaml
 

 

이렇게 분리하면 현장에서 실수를 줄일 수 있습니다.

 

 

728x90
728x90