1296.37 포트의 필수/선택 설정

1. 필수 포트와 선택 포트의 구분

BehaviorTree.CPP 라이브러리에서 입력 포트의 필수/선택 구분은 기본값의 유무에 의해 결정된다. 기본값이 없는 입력 포트는 필수 포트(mandatory port)로서 XML 트리 정의에서 반드시 값이 지정되어야 하며, 기본값이 있는 입력 포트는 선택 포트(optional port)로서 XML에서의 지정을 생략할 수 있다.

static BT::PortsList providedPorts()
{
    return {
        // 필수 포트: 기본값 없음
        BT::InputPort<geometry_msgs::msg::PoseStamped>(
            "goal_pose",
            "내비게이션 목표 위치"),

        // 선택 포트: 기본값 있음
        BT::InputPort<double>(
            "timeout_sec",
            "30.0",
            "타임아웃 (초)")
    };
}

출력 포트(OutputPort)는 항상 선택적이다. XML에서 출력 포트의 블랙보드 키 매핑을 생략하면, 해당 출력은 블랙보드에 기록되지 않으며 setOutput() 호출이 무시된다(Faconti, 2022).

2. 필수 포트의 정의와 동작

2.1 정의

필수 포트는 기본값 없이 선언된 입력 포트이다.

BT::InputPort<T>(포트명, 설명)

2.2 getInput() 실패 동작

필수 포트가 XML에서 지정되지 않은 경우, getInput() 호출이 실패한다.

BT::NodeStatus onStart() override
{
    geometry_msgs::msg::PoseStamped goal;
    if (!getInput("goal_pose", goal))
    {
        // 필수 포트 미지정 → 실패
        RCLCPP_ERROR(node_->get_logger(),
            "'goal_pose' 입력이 제공되지 않음");
        return BT::NodeStatus::FAILURE;
    }
    // ...
}

2.3 XML 검증

BehaviorTree.CPP는 트리의 XML 파싱 단계에서 필수 포트의 지정 여부를 검증할 수 있다. 필수 포트가 미지정된 경우 트리 생성 시 경고 또는 오류가 발생한다.

<!-- 오류: 필수 포트 goal_pose 미지정 -->
<NavigateToPose timeout_sec="60.0" />

<!-- 정상: 필수 포트 지정됨 -->
<NavigateToPose goal_pose="{target}" timeout_sec="60.0" />

3. 선택 포트의 정의와 동작

3.1 정의

선택 포트는 기본값과 함께 선언된 입력 포트이다.

BT::InputPort<T>(포트명, 기본값_문자열, 설명)

3.2 기본값 적용 동작

선택 포트가 XML에서 지정되지 않은 경우, getInput() 호출 시 기본값이 사용된다.

BT::NodeStatus onStart() override
{
    double timeout;
    getInput("timeout_sec", timeout);
    // XML에서 미지정 → timeout = 30.0 (기본값)

    std::string server;
    getInput("server_name", server);
    // XML에서 미지정 → server = "navigate_to_pose" (기본값)
    // ...
}

3.3 XML에서의 생략

<!-- 선택 포트 생략: 기본값 사용 -->
<NavigateToPose goal_pose="{target}" />

<!-- 선택 포트 명시: 지정 값 사용 -->
<NavigateToPose goal_pose="{target}"
                 timeout_sec="60.0"
                 server_name="custom_server" />

4. 필수/선택 구분의 설계 기준

포트를 필수로 할 것인지 선택으로 할 것인지는 다음의 기준에 의해 판단한다.

4.1 필수 포트로 설계하여야 하는 경우

  1. 범용적 기본값이 존재하지 않는 경우: 목표 위치, 대상 객체, 경로 데이터 등 임무마다 달라지는 데이터
  2. 기본값 사용이 위험한 경우: 잘못된 값에 의해 안전 문제가 발생할 수 있는 데이터
  3. 노드의 주된 행동을 정의하는 경우: 행동의 핵심 파라미터
// 필수: 내비게이션 목표 (임무마다 다름)
BT::InputPort<geometry_msgs::msg::PoseStamped>(
    "goal_pose", "내비게이션 목표 위치")

// 필수: 집어올릴 객체 (임무마다 다름)
BT::InputPort<std::string>(
    "object_id", "집어올릴 객체 식별자")

// 필수: 추종할 경로 (동적으로 생성됨)
BT::InputPort<nav_msgs::msg::Path>(
    "path", "추종할 경로")

4.2 선택 포트로 설계하여야 하는 경우

  1. 합리적 기본값이 존재하는 경우: 타임아웃, 허용 오차, 인터페이스 이름 등
  2. 대부분의 사용 사례에서 동일한 값이 사용되는 경우: 좌표계 프레임, 서비스 품질 설정 등
  3. 세부 구성(fine-tuning) 파라미터인 경우: 플러그인 이름, 재시도 횟수 등
// 선택: 타임아웃 (합리적 기본값 존재)
BT::InputPort<double>(
    "timeout_sec", "30.0", "타임아웃 (초)")

// 선택: 액션 서버 이름 (관례적 기본값 존재)
BT::InputPort<std::string>(
    "server_name", "navigate_to_pose", "액션 서버 이름")

// 선택: 좌표계 프레임 (관례적 기본값 존재)
BT::InputPort<std::string>(
    "frame_id", "map", "좌표계 프레임")

// 선택: 플러그인 이름 (빈 문자열 = 서버 기본값)
BT::InputPort<std::string>(
    "planner_id", "", "경로 계획기 플러그인 이름")

5. 포트 유형별 필수/선택 특성 요약

포트 유형기본값 설정필수/선택XML 생략 시 동작
InputPort (기본값 없음)불가필수getInput() 실패
InputPort (기본값 있음)가능선택기본값 사용
BidirectionalPort (기본값 없음)불가필수getInput() 실패
BidirectionalPort (기본값 있음)가능선택기본값 사용
OutputPort해당 없음항상 선택setOutput() 무시

6. 코드 내 필수 포트 처리 패턴

6.1 조기 반환 패턴

BT::NodeStatus onStart() override
{
    // 필수 입력 검증
    geometry_msgs::msg::PoseStamped goal;
    if (!getInput("goal_pose", goal))
    {
        RCLCPP_ERROR(node_->get_logger(),
            "필수 입력 'goal_pose' 누락");
        return BT::NodeStatus::FAILURE;
    }

    // 선택 입력 (기본값 사용)
    double timeout;
    getInput("timeout_sec", timeout);  // 기본값 30.0

    // 행동 시작
    sendGoal(goal, timeout);
    return BT::NodeStatus::RUNNING;
}

6.2 Expected 기반 검증 패턴

BT::NodeStatus onStart() override
{
    auto goal_result = getInput<
        geometry_msgs::msg::PoseStamped>("goal_pose");

    if (!goal_result)
    {
        RCLCPP_ERROR(node_->get_logger(),
            "goal_pose: %s", goal_result.error().c_str());
        return BT::NodeStatus::FAILURE;
    }

    auto goal = goal_result.value();
    // ...
}

6.3 다중 필수 포트 검증 패턴

BT::NodeStatus onStart() override
{
    geometry_msgs::msg::PoseStamped start, goal;
    bool inputs_valid = true;

    if (!getInput("start_pose", start))
    {
        RCLCPP_ERROR(node_->get_logger(),
            "필수 입력 'start_pose' 누락");
        inputs_valid = false;
    }

    if (!getInput("goal_pose", goal))
    {
        RCLCPP_ERROR(node_->get_logger(),
            "필수 입력 'goal_pose' 누락");
        inputs_valid = false;
    }

    if (!inputs_valid)
        return BT::NodeStatus::FAILURE;

    // 모든 필수 입력이 유효한 경우
    computePath(start, goal);
    return BT::NodeStatus::RUNNING;
}

7. 실용적 포트 설계 사례

7.1 내비게이션 노드

class NavigateToPose : public BT::StatefulActionNode
{
public:
    static BT::PortsList providedPorts()
    {
        return {
            // 필수 입력
            BT::InputPort<geometry_msgs::msg::PoseStamped>(
                "goal",
                "목표 위치와 자세 (필수)"),

            // 선택 입력
            BT::InputPort<std::string>(
                "server_name",
                "navigate_to_pose",
                "액션 서버 이름"),
            BT::InputPort<std::string>(
                "behavior_tree",
                "",
                "사용할 행동 트리 파일"),

            // 선택 출력
            BT::OutputPort<int>(
                "error_code",
                "내비게이션 오류 코드")
        };
    }
    // ...
};
<!-- 최소 사용: 필수 포트만 지정 -->
<NavigateToPose goal="{target}" />

<!-- 완전 사용: 모든 포트 지정 -->
<NavigateToPose goal="{target}"
                 server_name="nav2_navigate_to_pose"
                 behavior_tree="custom_bt.xml"
                 error_code="{nav_error}" />

7.2 센서 처리 노드

class DetectObject : public BT::StatefulActionNode
{
public:
    static BT::PortsList providedPorts()
    {
        return {
            // 필수 입력
            BT::InputPort<std::string>(
                "object_class",
                "검출 대상 객체 클래스 (필수)"),

            // 선택 입력
            BT::InputPort<double>(
                "confidence_threshold",
                "0.8",
                "검출 신뢰도 임계값"),
            BT::InputPort<std::string>(
                "camera_topic",
                "/camera/image_raw",
                "카메라 토픽 이름"),

            // 선택 출력
            BT::OutputPort<geometry_msgs::msg::Pose>(
                "object_pose",
                "검출된 객체 위치"),
            BT::OutputPort<double>(
                "confidence",
                "검출 신뢰도")
        };
    }
    // ...
};

8. 설계 지침

  1. 행동 파라미터는 필수, 구성 파라미터는 선택: 행동의 목표와 대상을 결정하는 핵심 파라미터는 필수로, 세부 구성을 제어하는 파라미터는 기본값과 함께 선택으로 설계한다.

  2. 필수 포트의 수 최소화: 필수 포트가 많으면 XML 정의가 복잡해진다. 합리적 기본값이 가능한 파라미터는 선택으로 전환한다.

  3. 필수 포트 실패의 명시적 처리: 코드 내에서 모든 필수 포트의 getInput() 반환값을 검사하고, 실패 시 구체적 오류 메시지와 함께 FAILURE를 반환한다.

  4. 출력 포트는 항상 선택적: 출력 포트의 XML 매핑이 생략될 수 있음을 전제하고, setOutput() 호출이 매핑 여부에 무관하게 안전하게 동작하도록 한다.

  5. 필수/선택의 문서화: 포트의 설명에 필수 여부를 명시하여, XML 작성자가 어떤 포트를 반드시 지정하여야 하는지 쉽게 파악할 수 있게 한다(Faconti, 2022).