1291.73 행동 트리 내부의 상태 머신 내장

하이브리드 아키텍처의 또 다른 구현 형태로서, 행동 트리(Behavior Tree, BT)의 특정 액션 노드 내부에 유한 상태 머신(Finite State Machine, FSM)을 내장(Embed)하는 설계 패턴이 존재한다. 이 패턴에서 행동 트리는 전체 임무의 구조와 반응적 행동 전환을 관리하며, 상태 의존적이고 절차적인 하위 작업은 액션 노드 내부의 유한 상태 머신에 의해 실행된다.

1. 아키텍처 구조

이 패턴에서 행동 트리의 특정 액션 노드는 단순한 단일 행동이 아니라 내부적으로 유한 상태 머신을 구동하는 복합 노드로 구현된다. 해당 액션 노드가 틱(Tick)을 수신하면 내부 유한 상태 머신의 현재 상태에 따른 행동을 실행하고, 상태 전이를 수행한다. 내부 유한 상태 머신이 최종 상태에 도달하면 액션 노드는 Success를 반환하고, 오류 상태에 도달하면 Failure를 반환하며, 아직 진행 중이면 Running을 반환한다.

BehaviorTree
├── ReactiveSequence
│   ├── Condition: 배터리 충분?
│   └── Fallback
│       ├── Sequence
│       │   ├── Condition: 목표 존재?
│       │   └── Action: 도킹 수행 [내부 FSM]
│       │       ├── FSM State: 접근
│       │       ├── FSM State: 정렬
│       │       ├── FSM State: 체결
│       │       └── FSM State: 확인
│       └── Action: 대기
└── Action: 충전 복귀

위 예시에서 “도킹 수행” 액션 노드의 내부에는 접근(Approach), 정렬(Align), 체결(Dock), 확인(Verify)의 4개 상태로 구성된 유한 상태 머신이 내장되어 있다. 도킹 절차는 엄격한 순서가 보장되어야 하므로 유한 상태 머신이 적합하며, 도킹 행동 전체에 대한 반응적 조건 검사(배터리 상태 등)는 상위 행동 트리에 의해 관리된다.

2. 적용 적합 영역

이 패턴은 다음의 특성을 갖는 하위 작업에 특히 적합하다.

첫째, 엄격한 순차적 단계가 요구되는 절차이다. 로봇 도킹, 매니퓰레이터의 공구 교환, 센서 캘리브레이션 등의 작업은 특정 단계를 반드시 순서대로 수행해야 하며, 단계 간의 전이 조건이 명확하다. 이러한 절차를 행동 트리의 시퀀스 노드만으로 구현하면 예외 처리와 재시도 논리가 복잡해지지만, 유한 상태 머신으로 캡슐화하면 각 상태에서의 전이 조건과 오류 처리를 명시적으로 정의할 수 있다.

둘째, 프로토콜 기반 상호 작용이다. 로봇 간의 핸드셰이크(Handshake) 프로토콜, 인간-로봇 상호 작용(Human-Robot Interaction, HRI)에서의 다단계 대화 흐름 등 상호 작용의 현재 단계에 따라 허용되는 응답이 엄격하게 제한되는 경우에 적합하다.

셋째, 하드웨어 제어 시퀀스이다. 모터 초기화, 센서 활성화, 통신 채널 설정 등 물리적 장치의 상태에 의존하는 제어 시퀀스에서, 현재 하드웨어 상태에 따른 명시적 전이 관리가 필요한 경우에 유리하다.

3. BehaviorTree.CPP에서의 구현

BehaviorTree.CPP 라이브러리에서 이 패턴은 StatefulActionNode를 상속하여 구현할 수 있다. onStart() 콜백에서 내부 유한 상태 머신을 초기 상태로 설정하고, onRunning() 콜백에서 현재 상태에 따른 행동을 실행하며 전이 조건을 평가한다. onHalted() 콜백에서는 내부 유한 상태 머신을 안전하게 정지시키고 리소스를 정리한다.

class DockingAction : public BT::StatefulActionNode {
    enum class DockState { APPROACH, ALIGN, DOCK, VERIFY, DONE, ERROR };
    DockState state_;

    BT::NodeStatus onStart() override {
        state_ = DockState::APPROACH;
        return BT::NodeStatus::RUNNING;
    }

    BT::NodeStatus onRunning() override {
        switch (state_) {
            case DockState::APPROACH:
                if (approach_complete()) state_ = DockState::ALIGN;
                return BT::NodeStatus::RUNNING;
            case DockState::ALIGN:
                if (aligned()) state_ = DockState::DOCK;
                return BT::NodeStatus::RUNNING;
            case DockState::DOCK:
                if (docked()) state_ = DockState::VERIFY;
                return BT::NodeStatus::RUNNING;
            case DockState::VERIFY:
                if (verified()) return BT::NodeStatus::SUCCESS;
                state_ = DockState::ERROR;
                return BT::NodeStatus::FAILURE;
            default:
                return BT::NodeStatus::FAILURE;
        }
    }

    void onHalted() override {
        safe_stop();
        state_ = DockState::APPROACH;
    }
};

4. 설계 고려 사항

이 패턴의 적용 시 다음의 설계 고려 사항이 존재한다.

첫째, 내부 상태의 외부 노출 범위이다. 내부 유한 상태 머신의 현재 상태를 블랙보드에 노출할 것인지의 결정이 필요하다. 디버깅과 모니터링을 위해 노출이 유리하지만, 외부 노드가 내부 상태에 의존하게 되면 캡슐화가 훼손된다.

둘째, Halt 처리의 안전성이다. 상위 행동 트리의 리액티브 노드에 의해 내부 유한 상태 머신이 임의의 상태에서 중단될 수 있으므로, 모든 상태에서의 안전한 중단 절차가 보장되어야 한다. 특히 하드웨어 제어를 수행하는 상태에서의 중단은 물리적 안전에 직결된다.

셋째, 재진입(Reentry) 정책이다. 내부 유한 상태 머신이 중단된 후 동일 액션 노드가 재실행될 때, 초기 상태에서 다시 시작할 것인지 또는 중단 시점의 상태를 복원할 것인지를 결정해야 한다. 이 정책은 내부 유한 상태 머신의 onStart() 콜백에서의 상태 초기화 여부에 의해 결정된다.


참고 문헌

  • Colledanchise, M., & Ögren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
  • Faconti, D., & Aurys, M. (2022). BehaviorTree.CPP Documentation. https://www.behaviortree.dev/
  • Iovino, M., Scukins, E., Styrud, H., Ögren, P., & Smith, C. (2022). “A Survey of Behavior Trees in Robotics and AI.” Robotics and Autonomous Systems, 154, 104096.