1296.33 입력 포트 (InputPort)의 정의와 활용
1. InputPort의 정의
BT::InputPort<T>는 BehaviorTree.CPP 라이브러리에서 노드가 블랙보드로부터 데이터를 읽기 위해 선언하는 포트이다. 입력 포트는 노드가 외부로부터 필요로 하는 데이터의 명칭, 타입, 설명, 기본값을 정의하며, 노드의 providedPorts() 정적 메서드에서 선언된다. 노드 내부에서는 getInput() 메서드를 통해 입력 포트의 값을 읽는다.
입력 포트는 노드의 입력 인터페이스를 구성하는 요소로서, 노드가 어떤 데이터에 의존하는지를 명시적으로 선언하는 역할을 수행한다(Faconti, 2022).
2. InputPort의 선언 형식
InputPort<T>의 선언은 다수의 오버로드 형식을 가진다.
2.1 필수 입력 포트 (기본값 없음)
BT::InputPort<T>(포트명, 설명)
기본값이 없는 입력 포트는 XML에서 반드시 값이 지정되어야 한다. 값이 지정되지 않으면 getInput()이 실패한다.
// 필수 입력 포트 선언
BT::InputPort<geometry_msgs::msg::PoseStamped>(
"goal_pose",
"내비게이션 목표 위치와 자세")
2.2 선택적 입력 포트 (기본값 있음)
BT::InputPort<T>(포트명, 기본값, 설명)
기본값이 있는 입력 포트는 XML에서 값이 생략되면 기본값이 사용된다.
// 선택적 입력 포트 선언 (기본값 포함)
BT::InputPort<double>(
"timeout_sec",
"30.0",
"타임아웃 (초, 기본: 30.0)")
BT::InputPort<std::string>(
"server_name",
"navigate_to_pose",
"액션 서버 이름")
기본값은 문자열 형식으로 제공되며, getInput() 호출 시 타입 T로 변환된다.
3. 지원 타입
3.1 기본 타입
BehaviorTree.CPP는 기본 타입에 대한 문자열-타입 변환을 내장하고 있다.
| 타입 | XML 표현 예시 | 설명 |
|---|---|---|
int | "42" | 정수 |
double | "3.14" | 부동 소수점 |
float | "1.5" | 단정도 부동 소수점 |
bool | "true" / "false" | 불리언 |
std::string | "hello" | 문자열 |
unsigned | "10" | 부호 없는 정수 |
3.2 사용자 정의 타입
기본 타입 이외의 타입을 입력 포트에 사용하려면, 해당 타입에 대한 문자열-타입 변환 함수를 BehaviorTree.CPP에 등록하여야 한다.
// Pose2D 사용자 정의 타입
struct Pose2D
{
double x, y, theta;
};
// 문자열-타입 변환 함수 등록
namespace BT
{
template <>
inline Pose2D convertFromString(StringView str)
{
auto parts = splitString(str, ';');
if (parts.size() != 3)
throw RuntimeError("Pose2D 변환 실패");
Pose2D output;
output.x = convertFromString<double>(parts[0]);
output.y = convertFromString<double>(parts[1]);
output.theta = convertFromString<double>(parts[2]);
return output;
}
} // namespace BT
변환 함수가 등록되면, XML에서 해당 타입의 값을 문자열로 지정할 수 있다.
<NavigateTo target="1.5;2.0;0.785" />
3.3 ROS2 메시지 타입
ROS2 메시지 타입은 블랙보드를 통해 전달될 때 타입 변환이 필요하지 않다. 블랙보드 키 참조({키명})로 값을 전달하면, 블랙보드의 Any 타입 저장소를 통해 원래의 타입이 보존된다.
BT::InputPort<geometry_msgs::msg::PoseStamped>(
"goal_pose", "목표 위치")
<!-- 블랙보드 키 참조: 타입 변환 불필요 -->
<NavigateToPose goal_pose="{current_goal}" />
<!-- 리터럴 값: convertFromString 필요 -->
<!-- PoseStamped에 대한 변환 함수가 등록되어야 함 -->
4. 값의 해석 과정
입력 포트에 지정된 값의 해석 과정을 단계별로 기술한다.
4.1 블랙보드 키 참조
XML에서 {키명} 형식으로 지정된 경우, getInput()은 블랙보드에서 해당 키의 값을 조회한다.
getInput("goal_pose", target)
│
├─ 포트 값: "{current_goal}" (블랙보드 키 참조)
│
├─ 블랙보드에서 "current_goal" 키 조회
│ │
│ ├─ 키 존재 → 값을 T 타입으로 캐스팅 → target에 대입
│ │
│ └─ 키 미존재 → 실패 반환
│
└─ 결과: 성공 또는 실패
4.2 리터럴 값
XML에서 리터럴 값이 지정된 경우, getInput()은 해당 문자열을 타입 T로 변환한다.
getInput("timeout_sec", timeout)
│
├─ 포트 값: "30.0" (리터럴)
│
├─ convertFromString<double>("30.0") 호출
│ │
│ ├─ 변환 성공 → timeout = 30.0
│ │
│ └─ 변환 실패 → 실패 반환
│
└─ 결과: 성공 또는 실패
4.3 기본값
XML에서 값이 지정되지 않고 기본값이 선언된 경우, 기본값이 사용된다.
getInput("timeout_sec", timeout)
│
├─ XML에서 미지정 → 기본값 "30.0" 사용
│
├─ convertFromString<double>("30.0") 호출
│
└─ timeout = 30.0
5. getInput()의 사용 패턴
5.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;
}
// goal 사용
sendNavigationGoal(goal);
return BT::NodeStatus::RUNNING;
}
5.2 Expected 반환값 활용
BehaviorTree.CPP 4.x에서 getInput()은 BT::Expected<T>를 반환하는 오버로드를 제공한다.
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();
sendNavigationGoal(goal);
return BT::NodeStatus::RUNNING;
}
Expected의 error() 메서드는 실패 원인을 문자열로 제공하므로, 디버깅에 유용하다.
6. 입력 포트의 읽기 시점
입력 포트의 값을 읽는 적절한 시점은 노드 유형에 따라 다르다.
| 노드 유형 | 권장 읽기 시점 | 이유 |
|---|---|---|
| SyncActionNode | tick() 시작부 | 매 Tick마다 최신 값 필요 |
| StatefulActionNode | onStart() | 행동 시작 시 입력 고정 |
| StatefulActionNode | onRunning() (선택적) | 동적 갱신 필요 시 |
| ThreadedAction | tick() 시작부 | 스레드 시작 전 입력 복사 |
| CoroActionNode | tick() 시작부 | 첫 실행 시 입력 고정 |
StatefulActionNode에서 onStart()에서 입력을 읽어 멤버 변수에 저장하면, onRunning()의 반복 호출에서 동일한 값을 사용할 수 있다. 이는 실행 중 블랙보드 값의 변경에 의한 비일관성을 방지한다.
class NavigateToPose : public BT::StatefulActionNode
{
geometry_msgs::msg::PoseStamped goal_;
BT::NodeStatus onStart() override
{
// onStart()에서 입력 읽기 → 멤버에 저장
if (!getInput("goal_pose", goal_))
return BT::NodeStatus::FAILURE;
sendNavigationGoal(goal_);
return BT::NodeStatus::RUNNING;
}
BT::NodeStatus onRunning() override
{
// goal_은 onStart()에서 저장된 값 사용
// 블랙보드 변경에 영향받지 않음
return isNavigationComplete()
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::RUNNING;
}
};
7. 입력 포트 실패의 원인과 처리
getInput()이 실패하는 주요 원인과 처리 방안을 정리한다.
| 실패 원인 | 상황 | 처리 |
|---|---|---|
| XML 미지정, 기본값 없음 | 필수 입력이 제공되지 않음 | FAILURE 반환 |
| 블랙보드 키 미존재 | 참조한 키가 블랙보드에 없음 | FAILURE 반환 |
| 타입 변환 실패 | 리터럴 값의 타입 변환 오류 | FAILURE 반환 + 로그 |
| 타입 불일치 | 블랙보드 값의 타입과 포트 타입 불일치 | FAILURE 반환 + 로그 |
모든 getInput() 호출의 반환값을 검사하는 것이 권장된다. 반환값을 검사하지 않으면, 유효하지 않은 값에 의한 미정의 동작이 발생할 수 있다.
8. 로봇 공학에서의 InputPort 활용 사례
8.1 내비게이션
static BT::PortsList providedPorts()
{
return {
BT::InputPort<geometry_msgs::msg::PoseStamped>(
"goal", "목표 위치와 자세"),
BT::InputPort<std::string>(
"planner_id", "",
"사용할 경로 계획기 플러그인"),
BT::InputPort<double>(
"goal_tolerance", "0.25",
"목표 도달 허용 오차 (미터)")
};
}
8.2 매니퓰레이션
static BT::PortsList providedPorts()
{
return {
BT::InputPort<std::string>(
"object_id", "집어올릴 객체 식별자"),
BT::InputPort<double>(
"grasp_force", "10.0",
"파지 힘 (뉴턴)"),
BT::InputPort<geometry_msgs::msg::Pose>(
"approach_pose", "접근 위치")
};
}
8.3 센서 처리
static BT::PortsList providedPorts()
{
return {
BT::InputPort<std::string>(
"camera_topic", "/camera/image_raw",
"카메라 토픽 이름"),
BT::InputPort<double>(
"confidence_threshold", "0.8",
"검출 신뢰도 임계값"),
BT::InputPort<std::string>(
"model_name", "yolov5",
"사용할 검출 모델 이름")
};
}
9. 설계 지침
-
필수/선택의 명확한 구분: 노드의 동작에 반드시 필요한 입력은 기본값 없이 선언하고, 구성 파라미터는 합리적 기본값과 함께 선언한다.
-
읽기 실패의 명시적 처리:
getInput()의 반환값을 항상 검사하고, 실패 시 적절한 로그와 함께 FAILURE를 반환한다. -
읽기 시점의 일관성:
StatefulActionNode에서는onStart()에서 입력을 읽어 멤버 변수에 저장하여, 실행 중 블랙보드 변경에 의한 비일관성을 방지한다. -
설명의 단위 포함: 물리량 파라미터의 설명에 단위(미터, 초, 라디안 등)를 명시한다.
-
타입의 의미적 정확성: 데이터의 의미에 부합하는 타입을 선택한다. 좌표는
PoseStamped, 경로는Path, 속도는Twist등 표준 ROS2 메시지 타입을 우선 활용한다(Faconti, 2022).