[ROS2] 파이썬 패키지 설계 및 프로그래밍(파라미터, 실행인자)

HY K·2024년 9월 20일

ROS2

목록 보기
13/18

이번 포스팅에서는 파이썬을 기반으로 하는 ROS2의 파라미터 및 실행인자 프로그래밍에 대해서 공부해보도록 하겠다.

참고한 링크 및 이미지 출처는 다음과 같다.
https://cafe.naver.com/openrt/24690
https://cafe.naver.com/openrt/24734

사용한 코드와 패키지는 모두 이전 포스팅과 이어진다.


파라미터 프로그래밍

파라미터

💡 파라미터
ROS2의 모든 노드는 파라미터 서버를 가지고 잇어서, 파라미터 클라이언트와의 서비스 통신을 통해서 파라미터에 접근이 가능하다. 이를 통해 ROS2의 파라미터는 일종의 글로벌 변수 역할을 통해 실시간으로 노드의 각종 값과 특성을 바꾸는 역할을 수행하는데 주로 사용된다.

💡 파라미터와 서비스의 차이

  • 파라미터는 특정 매개변수가 노드 내부 또는 외부에서 쉽게 읽고, 쓰고, 변경이 가능하도록 하는 것이다.
  • 서비스는 요청(request)과 응답(response)로 구성된 동기식 양방향 데이터 통신이다.

파라미터 설정

ROS2에서 파라미터를 사용하려면 다음 3가지 함수를 사용해야 한다.

1. declare_parameter 함수
노드에서 사용할 파라미터의 고유 이름을 지정하고 초깃값을 설정한다.

2. get_parameter 함수
노드에서 사용할 파라미터의 값을 불러오는 것으로, 선언된 파라미터의 고유 이름을 사용한다. 주로 .yaml 확장자를 가지는 파라미터 파일에 저장된 값을 불러오는데 사용한다.

3. add_on_set_parameters_callback 함수
서비스 형태로 파라미터 변경 요청이 있을 때 사용하는 함수로 지정한 콜백 함수를 호출한다.

예시 코드를 살펴보면 다음과 같다.

class Argument(Node):

    def __init__(self):
        super().__init__('argument')
        self.declare_parameter('qos_depth', 10)
        qos_depth = self.get_parameter('qos_depth').value
        self.declare_parameter('min_random_num', 0)
        self.min_random_num = self.get_parameter('min_random_num').value
        self.declare_parameter('max_random_num', 9)
        self.max_random_num = self.get_parameter('max_random_num').value
        self.add_on_set_parameters_callback(self.update_parameter)
    def update_parameter(self, params):
        for param in params:
            if param.name == 'min_random_num' and param.type_ == Parameter.Type.INTEGER:
                self.min_random_num = param.value
            elif param.name == 'max_random_num' and param.type_ == Parameter.Type.INTEGER:
                self.max_random_num = param.value
        return SetParametersResult(successful=True)

위 예시 코드를 보면 declare_parameter 함수로 파라미터를 선언하고, get_paramter 함수를 통해 파라미터 초깃값을 지정하는 것을 볼 수 있다. 그리고 지정 콜백 함수인 update_parameter 함수를 통해 파라미터 변경 요청을 수행할 수 있다.

CLI 기반 파라미터 사용 방법

먼저 실습을 위해 argument 노드를 실행하자.

$ ros2 run topic_service_action_rclpy_example argument

1. 파라미터 목록 확인

$ ros2 param list

현재 실행 중인 모든 노드의 모든 파라미터를 보여준다.

2. 현재 파라미터 값 확인

$ ros2 param get <node_name> <param_name>

예시를 들면 다음과 같다.

$ ros2 param get /argument max_random_num

이러면 결과로 9가 출력될 것이다.

3. 파라미터 값 변경

$ ros2 param set <node_name> <param_name> <value>

예를 들면 다음과 같다.

$ ros2 param set /argument max_random_num 100

이렇게 되면 max_random_num 파라미터가 100으로 변경된다.

파라미터 사용 방법(via. 서비스 클라이언트)

CLI 기반 말고도, 서비스 클라이언트를 통해서 서비스 요청으로 파라미터 값을 변경할 수 있는 방법도 있다. 이때 클라이언트 선언 부분 및 서비스를 요청하는 부분까지는 일반적인 서비스 클라이언트와 완전히 동일하다.

예시 코드를 보면 다음과 같다.

from rcl_interfaces.msg import Parameter
from rcl_interfaces.msg import ParameterType
from rcl_interfaces.msg import ParameterValue
from rcl_interfaces.srv import SetParameters

(중략)

    self.random_num_parameter_client = self.create_client(
        SetParameters,
        'argument/set_parameters')

(중략)
    def set_max_random_num_parameter(self, max_value):
        request = SetParameters.Request()
        parameter = ParameterValue(type=ParameterType.PARAMETER_INTEGER, integer_value=max_value)
        request.parameters = [Parameter(name='max_random_num', value=parameter)]
        service_client = self.random_num_parameter_client
        return self.call_service(service_client, request, 'max_random_num parameter')

    def call_service(self, service_client, request, service_name):
        wait_count = 1
        while not service_client.wait_for_service(timeout_sec=0.1):
            if wait_count > 3:
                self.get_logger().warn(service_name + ' service is not available.')
                return False
            wait_count += 1
        service_client.call_async(request)
        return True

기본 파라미터 설정 방법

많은 수의 파라미터를 한번에 선언(declare)하고, 읽고(get), 변경(set)하는 것은 직접 코드로 작성하기 어렵다. 이 때문에 ROS2에서는 YAML 형식으로 파라미터 파일을 작성하고, 이를 launch 파일에서 불러오도록 설정한다.

이를 위해서는 패키지 내부에 param 폴더를 만들고, 거기에 내가 원하는 yaml 파일을 만들어 넣으면 된다.

예시 yaml 파일은 다음과 같다.

/**: # namespace and node name
  ros__parameters:
    qos_depth: 30
    min_random_num: 0
    max_random_num: 9

/** 기호의 경우, 원래 저 위치에 namespace 및 node 이름을 작성해줘야 하나 이 부분은 변경의 가능성이 매우 크기 때문에 해당 기호를 통해서 간단히 생략하는 것이다.

이후 launch 파일에서 LaunchConfiguration 함수의 인자로 파라미터 파일의 경로를 저장하고, LaunchDescription 함수의 인자로 Node 함수를 이용해 parameter 인자에 앞서 yaml 파일의 경로를 지정하면 된다.

예시 코드는 다음과 같다.

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    param_dir = LaunchConfiguration(
        'param_dir',
        default=os.path.join(
            get_package_share_directory('topic_service_action_rclpy_example'),
            'param',
            'arithmetic_config.yaml'))

    return LaunchDescription([
        DeclareLaunchArgument(
            'param_dir',
            default_value=param_dir,
            description='Full path of parameter file'),

        Node(
            package='topic_service_action_rclpy_example',
            executable='argument',
            name='argument',
            parameters=[param_dir],
            output='screen'),

        Node(
            package='topic_service_action_rclpy_example',
            executable='calculator',
            name='calculator',
            parameters=[param_dir],
            output='screen'),
    ])

파라미터 폴더의 경로를 읽어와 지정하고, 해당 파라미터를 노드 실행에 포함시키는 것을 확인할 수 있다.

그리고 이러한 launch.py와 yaml 파일은 setup.py 파일에도 꼭 넣어줘야 한다.
예시는 다음과 같다.

        (share_dir + '/launch', glob.glob(os.path.join('launch', '*.launch.py'))),
        (share_dir + '/param', glob.glob(os.path.join('param', '*.yaml'))),

실행 인자 프로그래밍

ROS2 프로그램 실행 시 옵션으로 인수를 추가하여 실행하는 경우가 있다 . 이때 사용하는 인자를 실행인자(argument)라고 하며, main 함수에서 매개변수를 통해 접근할 수 있다.

💡 파이썬의 실행인자 처리

  • 인수들을 무시할 때는 args=None으로 하고 rclpy.init 함수에 넘긴다.
  • 인자들을 사용하고자 한다면, argv(Argument Vector)에 매개변수를 저장하고 이를 rclpy.init 함수의 args 매개변수로 넘긴다.
  • 이후 argparse 모듈을 이용해 실행 인자를 위한 구문 해석 프로그램을 작성한다. argparse 모듈은 파이썬 표준 parser 라이브러리이다.
def main(argv=sys.argv[1:]):
	(argparse 구문)
    rclpy.init(args=argv)
    (이하 생략)

예시 코드를 통해서 한번 알아보자.

import argparse
import sys

import rclpy

from topic_service_action_rclpy_example.checker.checker import Checker


def main(argv=sys.argv[1:]):
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '-g',
        '--goal_total_sum',
        type=int,
        default=50,
        help='Target goal value of total sum')
    parser.add_argument(
        'argv', nargs=argparse.REMAINDER,
        help='Pass arbitrary arguments to the executable')
    args = parser.parse_args()

    rclpy.init(args=args.argv)
    try:
        checker = Checker()
        checker.send_goal_total_sum(args.goal_total_sum)
        try:
            rclpy.spin(checker)
        except KeyboardInterrupt:
            checker.get_logger().info('Keyboard Interrupt (SIGINT)')
        finally:
            checker.arithmetic_action_client.destroy()
            checker.destroy_node()
    finally:
        rclpy.shutdown()


if __name__ == '__main__':
    main()

여기서 핵심은 다음과 같다.
1. parser 만들기
2. 인자 추가하기
3. 인자 파싱하기
4. 인자 사용하기

parser 만들기

이를 위해 필요한 것은 다음과 같다.

  • argparse 모듈의 ArgumentParser 클래스를 이용한다.
  • 위 코드에서는 parser 변수를 위 클래스를 이용해 선언한다.
  • 여기서 formatter_class는 argparse 모듈의 가장 기본적 형식을 사용하도록 설정한다.
def main(argv=sys.argv[1:]):
	parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpoFormatter)

인자 추가하기

실행 인자로 사용할 인자를 추가하려면 add_argument 함수를 호출하고, 해당 함수의 매개변수를 채우면 된다.

parser.add_argument(
	'-g', // 간략한 인자 이름
    '--goal_total_sum', // 상세한 인자 이름
    type=int,
    default=50,
    help='Target Goal Value of Total Sum')

마지막 설명을 통해 -h 혹은 --help 인자를 입력하였을 때 도움말을 출력할 수 있다.

인자 parsing 하기

인자 추가가 끝났다면, parse_args() 메서드를 통해 인자를 파싱하면 된다.

args = parser.parser_args()

인자 사용하기

인자에 접근하기 위해서는 인자를 파싱하여 대입한 args 변수를 사용해야 한다. 예를 들어 위에 코드를 볼 경우, add_argument를 통해 인자로 추가했던 "--goal_total_sum"은 "args.goal_total_sum"으로 접근이 가능하다.

checker.send_goal_total_sum(args.goal_total_sum)
profile
로봇, 드론, SLAM, 제어 공학 초보

0개의 댓글