1317.39 사용자 정의 플래너 플러그인 개발
1. 사용자 정의 플래너의 필요성
PlanSys2는 POPF를 기본 플래너로 제공하지만, 특정 도메인이나 응용 시나리오에서는 다른 플래너가 더 적합할 수 있다. 예를 들어, 파생 술어(derived predicate)를 사용하는 도메인에서는 POPF 대신 Fast Downward가 필요하며, 확률적 도메인에서는 확률적 플래너가 요구된다. PlanSys2의 플러그인 아키텍처는 이러한 요구에 대응하여, 개발자가 자신만의 플래너 플러그인을 개발하고 통합할 수 있는 체계적 절차를 제공한다(Martín et al., 2021).
2. 개발 절차
2.1 단계: ROS2 패키지 생성
사용자 정의 플래너 플러그인을 위한 독립적인 ROS2 패키지를 생성한다. 이 패키지는 plansys2_core에 대한 빌드 의존성을 가진다.
find_package(plansys2_core REQUIRED)
find_package(pluginlib REQUIRED)
2.2 단계: PlanSolverBase 상속 클래스 구현
plansys2::PlanSolverBase 추상 클래스를 상속하는 플러그인 클래스를 구현한다.
#include "plansys2_core/PlanSolverBase.hpp"
namespace my_planner
{
class MyPlanSolver : public plansys2::PlanSolverBase
{
public:
void configure(
rclcpp_lifecycle::LifecycleNode::SharedPtr node,
const std::string & plugin_name) override;
std::optional<plansys2_msgs::msg::Plan> getPlan(
const std::string & domain,
const std::string & problem,
const std::string & node_namespace) override;
};
}
configure 메서드에서는 플래너 실행 파일 경로, 시간 제한, 탐색 옵션 등 플래너 고유 파라미터를 ROS2 파라미터 시스템으로부터 로딩한다. getPlan 메서드에서는 도메인과 문제 PDDL 문자열을 수신하여, 외부 플래너를 호출하고 결과를 파싱하여 반환하는 전체 로직을 구현한다.
2.3 단계: getPlan 메서드의 구현
getPlan 메서드의 전형적 구현 패턴은 다음과 같다.
- 임시 파일 작성: 수신된 도메인과 문제 PDDL 문자열을 임시 파일로 저장한다.
- 외부 프로세스 실행:
std::system()또는 POSIXfork()/exec()계열 함수를 사용하여 플래너 프로세스를 생성한다. - 종료 대기 및 결과 확인: 프로세스의 종료를 대기하고 종료 코드를 확인한다.
- 출력 파싱: 플래너의 출력을 읽어
Plan메시지의PlanItem배열로 변환한다. - 임시 파일 정리: 생성된 임시 파일을 삭제한다.
- 결과 반환: 성공 시
Plan메시지를, 실패 시std::nullopt을 반환한다.
2.4 단계: 플러그인 등록
소스 파일에 PLUGINLIB_EXPORT_CLASS 매크로를 추가하여 플러그인을 pluginlib에 등록한다.
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(
my_planner::MyPlanSolver,
plansys2::PlanSolverBase)
2.5 단계: 플러그인 기술 XML 작성
플러그인의 메타데이터를 기술하는 XML 파일을 작성한다.
<library path="my_plan_solver">
<class type="my_planner::MyPlanSolver"
base_class_type="plansys2::PlanSolverBase">
<description>Custom planner plugin</description>
</class>
</library>
2.6 단계: CMakeLists.txt 구성
공유 라이브러리의 빌드와 플러그인 XML 파일의 등록을 CMakeLists.txt에 설정한다.
add_library(my_plan_solver SHARED src/my_plan_solver.cpp)
ament_target_dependencies(my_plan_solver plansys2_core pluginlib)
pluginlib_export_plugin_description_file(plansys2_core my_planner_plugin.xml)
install(TARGETS my_plan_solver LIBRARY DESTINATION lib)
2.7 단계: 런치 파일 구성
개발된 플러그인을 PlanSys2에서 사용하기 위해 런치 파일의 파라미터를 구성한다.
planner:
ros__parameters:
plan_solver_plugins: ["MyPlanner"]
MyPlanner:
plugin: "my_planner/MyPlanSolver"
timeout: 30.0
3. 출력 파싱의 구현
플래너마다 출력 형식이 상이하므로, 출력 파싱 로직은 플러그인 개발에서 가장 주의가 필요한 부분이다. 주요 고려사항은 다음과 같다.
- 시간적 계획과 순차 계획의 구분: 플래너가 시간 정보를 포함하는 출력을 생성하는지 여부에 따라 파싱 방식이 달라진다.
- 주석과 메타데이터의 필터링: 플래너 출력에 포함된 통계 정보, 주석 등을 계획 항목과 분리해야 한다.
- 인코딩 차이의 처리: 일부 플래너는 SAS+ 인코딩이나 고유한 중간 표현을 사용하므로, PDDL 수준의 액션으로 역변환하는 과정이 필요할 수 있다.
4. 테스트와 검증
개발된 플러그인은 다음과 같은 테스트를 통해 검증해야 한다.
- 단위 테스트:
getPlan메서드가 알려진 도메인과 문제에 대해 유효한 계획을 반환하는지 확인한다. - 통합 테스트: PlanSys2의 전체 파이프라인에서 플러그인이 올바르게 로딩되고 동작하는지 확인한다.
- 오류 처리 테스트: 해 부재, 시간 초과, 잘못된 입력 등의 오류 상황에서 플러그인이 적절하게 반응하는지 확인한다(Martín et al., 2021).
참고 문헌
- Martín, F., Cañas, J. M., Ginés, J., & Fuentetaja, R. (2021). “PlanSys2: A Planning System Framework for ROS2.” IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS).
버전: v1.0