A.3 ROS2 행동 트리 (Behavior Tree)

A.3 ROS2 행동 트리 (Behavior Tree)

ROS2 Humble 환경에서 행동 트리(Behavior Tree, BT)는 로봇의 복잡한 의사결정 및 제어 로직을 모듈화하고 계층적으로 구성하기 위한 수학적·논리적 아키텍처이다. 이는 유한 상태 기계(Finite State Machine, FSM)의 한계를 극복하고 코드의 재사용성과 확장성을 높이기 위해 고안되었으며, ROS2 생태계에서는 주로 BehaviorTree.CPP 라이브러리를 통해 구현된다. 특히 Nav2(Navigation2) 스택의 작업 흐름(Task Flow) 제어에 핵심적으로 사용된다.

1. 상태 머신(FSM)과 행동 트리(BT) 비교

비교 항목상태 머신 (FSM)행동 트리 (BT)
구조적 특징상태(State)와 전이(Transition)의 네트워크(Graph) 구조제어 흐름 노드(Node)들의 계층적 트리(Tree) 구조
설계 패러다임시스템이 ’어떤 상태에 있는가’에 초점시스템이 ’무엇을 해야 하는가(Task)’에 초점
모듈화 및 재사용상태 간 결합도가 높아 부분적 분리 및 재사용이 어려움서브 트리를 독립적인 모듈로 분리하여 재사용 용이
확장성상태 추가 시 기존 전이 조건을 모두 수정해야 함(상태 폭발)기존 구조 변경 없이 트리 구조의 특정 위치에 새 노드 추가
반응성(Reactivity)예외 상황 처리를 위해 전역 전이(Global Transition) 설계 필요틱(Tick) 신호의 하향식 전파로 변화하는 환경에 즉각적 대응

2. 로봇 분야에서 행동 트리 채택이 타당한 학술적 이유

  • 동적 환경에 대한 반응성(Reactivity): 로봇은 예측 불가능한 물리적 환경에서 작동한다. 행동 트리는 최상위 노드에서부터 주기적으로 전체 상태를 재평가(Tick)하므로, 센서 노이즈나 갑작스러운 장애물 출현 같은 외부 교란(Disturbance)이 발생했을 때 즉각적으로 하던 행동을 중단하고 회피 등의 우선순위 행동으로 전환할 수 있다.
  • 소프트웨어 공학적 모듈화: 로봇의 임무는 ‘이동’, ‘물체 인식’, ‘파지(Grasping)’ 등 여러 하위 시스템의 결합으로 이루어진다. 행동 트리는 각 하위 임무를 독립적인 서브 트리로 개발하고 단위 테스트(Unit Test)를 거친 뒤 하나의 메인 트리로 통합할 수 있어 시스템 통합 비용을 획기적으로 낮춘다.
  • 제어 이론과의 융합: 최근 로봇 제어 학계에서는 제어 장벽 함수(Control Barrier Functions, CBF)나 모델 예측 제어(Model Predictive Control, MPC)와 같은 저수준 연속 시간 제어 알고리즘을 행동 트리의 리프 노드(Leaf Node, Action)와 결합한다 이를 통해 상위 수준의 논리적 의사결정과 하위 수준의 물리적 제어를 수학적으로 통합하여 시스템 전체의 안정성을 증명한다.

결론적으로, 로봇 공학에서의 행동 트리는 단순한 구현 편의성을 넘어 복잡한 시스템의 계층적 모듈화, 비정형 환경에서의 강건한 반응성, 그리고 제어 이론 기반의 수학적 검증 가능성을 모두 제공하는 핵심 임무 관리 아키텍처다.

3. 노드

ROS2 생태계와 행동 트리 라이브러리가 결합된 환경에서 ’노드(Node)’라는 용어는 서로 다른 두 가지 시스템의 구성 요소를 지칭하므로 학술적, 명확한 구분이 필요하다. 두 개념의 정의와 아키텍처적 종속 관계는 다음과 같다.

3.1 ROS2 노드 (ROS 2 Node, rclcpp::Node)

  • 정의: ROS2 미들웨어(DDS) 네트워크 상에서 퍼블리셔(Publisher), 서브스크라이버(Subscriber), 서버 및 클라이언트로 동작하며 통신에 참여하는 독립적인 실행 단위이다.
  • 역할: 로봇 시스템의 모듈간 데이터를 교환하고 시스템 자원을 할당받아 실행된다.
  • 소프트웨어 특성: 운영체제(OS) 관점에서 독립적인 프로세스(Process) 또는 컴포저블 노드(Composable Node) 형태의 스레드(Thread)로 동작하며, 고유한 실행 컨텍스트와 메모리 공간을 갖는다.

3.2 행동 트리 노드 (Behavior Tree Node, BT::TreeNode)

  • 정의: 행동 트리 아키텍처 내부에서 논리적 연산, 흐름 제어, 또는 단일 작업을 정의하는 자료구조의 원소이자 C++ 객체(Object)이다.
  • 역할: 트리 구조 내에서 루트로부터 전달되는 틱(Tick) 신호를 처리하고 SUCCESS, FAILURE, RUNNING 상태 중 하나를 반환한다. (예: Sequence, Selector, Action Node 등)
  • 소프트웨어 특성: 독립적인 프로세스나 스레드가 아니며, 호스트 프로그램의 메모리 힙(Heap) 영역에 생성되는 클래스 인스턴스에 불과하다.

3.3 구조적 포함 관계 (Composition)

행동 트리 노드는 ROS2 노드에 종속되는 포함 관계를 갖는다.

  1. 하나의 ROS2 노드 프로세스 내부에 행동 트리 실행 엔진(BT::Tree)이 인스턴스화된다.
  2. 이 트리 객체 내부에 다수의 행동 트리 노드(BT::TreeNode)들이 계층적 트리 구조로 배치된다.

실제 제어 시스템 구현 시, 특정 ’BT 액션 노드’가 모터 구동 명령을 내리려면 외부와 통신할 수 있는 기능이 필요하다. 이때 BT 노드는 자신이 속한 ’ROS2 노드’의 통신 핸들(Node Handle)을 포인터나 의존성 주입(Dependency Injection) 방식으로 전달받아 ROS2 액션 클라이언트(Action Client) 역할을 수행한다.

이후 설명에서는 혼동을 방지하기 위해 두 개념을 각각 **‘ROS2 노드’**와 **‘BT 노드’**로 엄격히 구분하여 명명하겠다.

4. 틱(Tick) 신호

행동 트리(Behavior Tree)에서 틱(Tick) 신호는 트리의 실행을 유도하고 제어 흐름을 하위 BT 노드로 전달하는 함수 호출(Function Invocation) 메커니즘이다. 루트(Root) BT 노드에서 시작된 실행 제어 신호인 ’틱(Tick)’을 하위 BT 노드로 전파(Propagation)하는 방식으로 동작한다. 틱을 수신한 BT 노드는 연산을 수행한 후 다음 세 가지 상태 중 하나를 상위 BT 노드로 반환한다.

  • SUCCESS: 작업이 성공적으로 완료됨.
  • FAILURE: 작업 수행에 실패하거나 조건이 충족되지 않음.
  • RUNNING: 비동기 작업이 진행 중이며, 다음 주기에서 다시 틱이 필요함.

4.1 틱(Tick)의 학술적 정의 및 특징

  • 실행 권한의 부여: 틱 신호는 루트(Root) BT 노드에서 시작되어 계층 구조를 따라 하위 BT 노드로 전파(Propagation)된다. 틱을 수신한 BT 노드만이 할당된 연산이나 논리 평가를 수행할 수 있는 권한을 획득한다.
  • 폴링(Polling) 기반 구동: 인터럽트(Interrupt)에 의해 반응하는 이벤트 주도(Event-driven) 방식과 달리, 틱은 시스템에 설정된 특정 주파수(Frequency, 예: 10Hz, 100Hz)에 따라 트리를 주기적으로 순회하며 상태를 검사하는 폴링 방식을 취한다.
  • 상태 반환의 의무: 틱을 수신하여 연산을 수행한 BT 노드는 반드시 제어권을 상위 BT 노드로 반환해야 하며, 이때의 반환 상태값은 논리적 결과에 따라 SUCCESS, FAILURE, RUNNING으로 엄격히 제한된다.

4.2 제어 흐름과 틱의 라우팅(Routing)

제어 노드(Control Nodes)는 하위 BT 노드로 틱 신호를 분배하는 라우터 역할을 수행한다.

  • Sequence 노드: 자식 BT 노드에 순차적으로 틱을 전달하며, 이전 자식 BT 노드가 SUCCESS를 반환했을 때만 다음 자식 노드로 틱을 전파한다.
  • Fallback (Selector) 노드: 자식 BT 노드에 순차적으로 틱을 전달하며, 이전 자식 BT 노드가 FAILURE를 반환했을 때만 다음 자식 노드로 대체 실행을 위한 틱을 전파한다.

4.3 비동기(Asynchronous) 제어와 RUNNING 상태의 처리

경로 이동이나 객체 인식 등 연산에 오랜 시간이 소요되는 작업(Action)은 단일 틱 주기 내에 연산을 완료할 수 없다.

  • 이러한 BT 노드는 틱을 수신했을 때 즉시 상위 BT 노드에 RUNNING 상태를 반환하여 메인 제어 스레드의 병목(Bottleneck)이나 블로킹(Blocking) 현상을 방지한다.
  • 이후 다음 실행 주기에 트리가 다시 틱 신호를 발생시키면, 이전에 RUNNING을 반환했던 BT 노드가 우선적으로 틱을 수신하여 중단되었던 내부 프로세스를 재개(Resume)한다.

5. 블랙보드 (Blackboard)

블랙보드(Blackboard)는 행동 트리(Behavior Tree) 아키텍처 내에서 독립적인 노드 간의 데이터 교환을 매개하는 공유 메모리(Shared Memory) 메커니즘이다.

행동 트리를 구성하는 각 노드는 재사용성을 위해 철저히 모듈화되어 있으며, 서로의 메모리 주소나 내부 상태를 직접 참조하지 않는다. 블랙보드는 이러한 시스템에서 노드 간 결합도(Coupling)를 최소화하면서 필요한 정보(상태, 센서 데이터, 제어 명령 등)를 전달하기 위한 중앙 저장소 역할을 수행한다.

5.1 주요 구성 요소 및 동작 원리

  • 키-값 쌍 (Key-Value Pair): 블랙보드 내의 데이터는 고유한 문자열인 ’키(Key)’와 그에 대응하는 ’값(Value)’의 형태로 저장된다.
  • 데이터 포트 (Data Ports): 노드가 블랙보드의 데이터에 접근하기 위한 인터페이스이다.
  • 입력 포트 (Input Port): 특정 키에 매핑된 값을 블랙보드로부터 읽어온다(Read). 주로 조건 노드나 액션 노드의 실행 매개변수로 사용된다.
  • 출력 포트 (Output Port): 노드의 연산 결과나 상태를 특정 키에 매핑하여 블랙보드에 기록한다(Write).
  • 포트 리매핑 (Port Remapping): XML을 통해 행동 트리를 구성할 때, 노드의 포트 이름과 블랙보드의 실제 키(Key) 이름을 동적으로 연결(Mapping)하여 C++ 코드의 재컴파일 없이 데이터 흐름을 재구성할 수 있다.

5.2 ROS2 환경에서의 적용 사례 (Nav2)

ROS2의 Nav2(Navigation2) 스택에서는 로봇의 자율 주행 과정에서 다음과 같은 순차적 논리 흐름에 블랙보드가 활용된다.

  1. 외부(예: RViz)에서 전달된 ’목표 좌표(Goal Pose)’가 블랙보드에 기록된다.
  2. 경로 계획 노드(ComputePathToPose)는 입력 포트를 통해 해당 좌표를 읽어와 연산하고, 결과물인 ’전역 경로(Global Path)’를 출력 포트를 통해 블랙보드에 기록한다.
  3. 경로 추종 노드(FollowPath)는 블랙보드에 기록된 ’전역 경로’를 입력 포트로 읽어와 모터 제어 명령을 생성하는 데 사용한다.

5.3 타입 안정성 (Type Safety)

BehaviorTree.CPP 라이브러리는 C++ 기반 환경에서 타입 안정성을 보장하기 위해 내부적인 데이터 컨테이너(예: std::any 또는 자체 구현 포트 시스템)를 운용한다. 이를 통해 기본 자료형뿐만 아니라 geometry_msgs::msg::PoseStamped와 같은 ROS2의 복잡한 메시지 타입이나 사용자 정의 구조체도 블랙보드 메모리 공간에 안전하게 적재하고 참조할 수 있다.

6. BT 노드(Node)의 분류와 구조

행동 트리의 BT 노드는 트리의 흐름을 제어하는 ’제어 노드’와 실제 작업을 수행하는 ’리프 노드’로 대별된다.

6.1 제어 노드 (Control Nodes)

하나 이상의 자식 BT 노드를 가지며, 자식 BT 노드의 반환 상태에 따라 다음 실행 흐름을 결정한다.

  • Sequence (순차): 자식 BT 노드들을 왼쪽에서 오른쪽으로 순차적으로 틱한다. 모든 자식 BT 노드가 SUCCESS를 반환해야만 SUCCESS를 반환하며, 하나라도 FAILURE를 반환하면 즉시 FAILURE를 반환한다. (논리곱, AND 연산과 유사)
  • Fallback / Selector (선택): 자식 BT 노드들을 순차적으로 틱하며, 하나라도 SUCCESS를 반환하면 즉시 SUCCESS를 반환한다. 모든 자식이 FAILURE를 반환해야 FAILURE를 반환한다. (논리합, OR 연산과 유사)
  • Parallel (병렬): 여러 자식 BT 노드를 동시에 틱한다. 설정된 M개의 자식 BT 노드가 SUCCESS(또는 FAILURE)를 반환할 때 전체 상태를 결정하는 NM (M-of-N) 정책을 사용한다.

6.2 데코레이터 노드 (Decorator Nodes)

단 하나의 자식 BT 노드만 가질 수 있으며, 자식 BT 노드의 반환 상태를 변환하거나 실행을 제어한다.

  • Inverter: 자식 BT 노드의 SUCCESS를 FAILURE로, FAILURE를 SUCCESS로 반전시킨다.
  • Retry: 자식 BT 노드가 FAILURE를 반환할 경우 지정된 횟수만큼 재실행(Tick)한다.

6.3 리프 노드 (Leaf Nodes)

자식 BT 노드를 가지지 않으며, 실질적인 로봇 제어나 상태 평가 연산을 수행한다.

  • Condition Node (조건 노드): 시스템의 상태나 센서 값을 즉각적으로 평가한다. 연산 지연이 없으므로 RUNNING 상태를 반환하지 않고, 오직 SUCCESS 또는 FAILURE만 반환한다.
  • Action Node (액션 노드): 모터 제어, 경로 탐색 등 시간이 소요되는 작업을 수행한다. 작업이 완료될 때까지 RUNNING 상태를 반환하며, ROS2의 Action Client와 연동되어 비동기적으로 동작하는 경우가 많다.

7. ROS2 와의 통합 (Integration)

ROS2에서 행동 트리를 구현할 때는 런타임 유연성을 확보하기 위해 로직을 XML 파일로 분리하여 정의한다.

  • XML 직렬화/역직렬화: 트리의 계층 구조와 포트 매핑은 XML 형식으로 작성되며, BehaviorTree.CPPTreeFactory 객체를 통해 런타임에 파싱되어 인스턴스화된다.
  • ROS2 인터페이스 연동: 커스텀 Action 노드나 Condition 노드를 C++로 작성할 때, 노드 내부에서 ROS2 Node 핸들을 멤버 변수로 가져와 Topic(Publish/Subscribe), Service(Client), Action(Client) 통신을 구현한다.

8. C++ 구현 명세

BehaviorTree.CPP 프레임워크를 활용하여 ROS2 환경에서 작동하는 커스텀 행동 트리(BT) 노드를 C++로 구현하기 위한 학술적, 구조적 명세는 다음과 같다.

8.1 기저 클래스(Base Class)의 상속 구조

커스텀 BT 노드는 수행할 작업의 특성(동기/비동기, 상태 유무)에 따라 BehaviorTree.CPP가 제공하는 특정 기저 클래스를 상속받아 구현해야 한다.

  • BT::SyncActionNode (동기 액션 노드): 단일 틱(Tick) 주기 내에 연산이 완료되는 작업에 사용한다. 제어 흐름의 블로킹(Blocking)이 발생하므로 복잡한 연산이나 I/O 대기에는 부적합하다. SUCCESS 또는 FAILURE만 반환할 수 있으며, RUNNING 반환은 아키텍처 설계상 허용되지 않는다.
  • BT::StatefulActionNode (상태 기반 비동기 액션 노드): 로봇의 이동, 장기 체류 연산 등 여러 틱에 걸쳐 수행되는 비동기 작업에 사용한다. 내부 상태 기계(Internal State Machine)를 가지며, 다음 세 가지 가상 함수를 반드시 오버라이딩(Overriding)해야 한다.
  • onStart(): 노드가 비활성 상태에서 처음 틱을 받을 때 호출된다. 연산 결과에 따라 RUNNING, SUCCESS, FAILURE 반환이 가능하다.
  • onRunning(): 이전 틱에서 RUNNING을 반환한 후, 다음 주기에서 다시 틱을 받을 때 호출되어 작업을 재개한다.
  • onHalted(): 외부(예: 상위 제어 노드의 조건 변경)에 의해 실행이 강제 중단(Preempt)될 때 호출되어 메모리 및 스레드 자원을 정리한다.
  • BT::ConditionNode (조건 노드): 특정 논리적 참/거짓 또는 센서 상태를 즉각적으로 평가할 때 상속한다. 동기 액션 노드와 마찬가지로 지연 없는 연산을 전제로 하므로 RUNNING을 반환할 수 없다.

8.2 블랙보드 데이터 포트(Ports) 정의

노드가 블랙보드와 데이터를 교환하기 위해서는 정적 멤버 함수인 providedPorts()를 통해 입출력 인터페이스를 명시적으로 선언해야 한다.

  • 선언 방식: static BT::PortsList providedPorts() 함수를 구현하여 노드가 요구하는 BT::InputPort<T>BT::OutputPort<T>의 리스트를 반환한다.
  • 타입 명시: <T> 템플릿 인자를 통해 입출력되는 데이터의 C++ 자료형(예: std::string, geometry_msgs::msg::PoseStamped 등)을 엄격히 규정하여 런타임 타입 오류를 방지하고 메모리 안정성을 확보한다.

8.3 ROS2 의존성 주입 (Dependency Injection)

커스텀 BT 노드가 ROS2의 퍼블리셔, 서브스크라이버, 또는 액션 클라이언트로 기능하려면 rclcpp::Node의 인스턴스 포인터(통상 std::shared_ptr<rclcpp::Node>)가 필요하다. 이를 BT 노드 내부로 전달하기 위해 주로 다음 두 가지 아키텍처 패턴이 적용된다.

  • 생성자를 통한 직접 주입 (Constructor Injection): 트리 인스턴스를 생성하기 전, C++ 메인 프로세스에서 생성된 ROS2 노드 포인터를 커스텀 BT 노드의 생성자 매개변수로 직접 전달한다.
  • 블랙보드를 통한 공유 (Blackboard Sharing): 최상위 트리 범위의 전역 블랙보드에 ROS2 노드 포인터를 특수 키(예: “node_handle”)로 저장한 뒤, 각 하위 커스텀 노드가 초기화될 때 포트 입력 기능(getInput<T>)을 사용하지 않고 블랙보드 포인터(config().blackboard)를 통해 다운캐스팅하여 참조한다. Nav2 스택에서 시스템 결합도를 낮추기 위해 널리 채택되는 방식이다.

8.4 팩토리(Factory) 등록 및 직렬화

작성된 C++ 클래스는 독립적으로 실행될 수 없으며, 반드시 BT::BehaviorTreeFactory 인스턴스에 등록되어야 XML 파싱 단계에서 인스턴스화가 가능하다.

  • 등록 메커니즘: 메인 실행 프로세스에서 팩토리 객체를 생성한 후, factory.registerNodeType<CustomNodeClassName>("XML_Node_Name") 형태의 템플릿 함수를 호출하여 노드를 레지스트리에 등록한다.
  • XML 매핑: 등록 시 지정한 문자열 식별자(“XML_Node_Name”)가 런타임 시 로드되는 XML 트리 정의 파일의 태그 이름과 정확히 일치해야 C++ 객체와 논리적 노드가 성공적으로 매핑된다.

8.5 반환 상태(NodeStatus)의 명시

모든 논리 평가 및 작업 함수의 종료 지점에서는 실행 흐름 제어를 위해 BT::NodeStatus 열거형(Enum) 상수를 반드시 반환해야 한다.

  • return BT::NodeStatus::SUCCESS; (성공적 완료)
  • return BT::NodeStatus::FAILURE; (실패 또는 조건 불충족)
  • return BT::NodeStatus::RUNNING; (비동기 처리 진행 중, 다음 틱 요구)

9. 행동 트리(Behavior Tree)의 XML 기반 구조 및 명세(Specification)

ROS2 생태계와 BehaviorTree.CPP 라이브러리 환경에서 행동 트리의 구조는 C++ 소스 코드가 아닌 XML(eXtensible Markup Language) 파일 포맷을 통해 정의된다. 이는 시스템 아키텍처 관점에서 데이터와 제어 로직을 엄격하게 분리(Decoupling)하기 위한 설계 기법이다.

9.1 XML 도입의 학술적·공학적 목적

  • 동적 유연성(Dynamic Flexibility): 트리의 계층 구조나 노드 간의 실행 순서를 변경할 때마다 C++ 코드를 재컴파일(Recompile)할 필요 없이, 런타임(Runtime)에 XML 파일만 로드하여 시스템의 동작을 수정할 수 있다.
  • 시각화 및 상호운용성: XML로 직렬화(Serialization)된 트리 구조는 ’Groot’와 같은 공식 GUI 편집 도구와 호환된다. 이를 통해 복잡한 로직을 시각적으로 설계하고 디버깅할 수 있다.
  • 모듈의 재사용성 극대화: 노드의 C++ 구현체는 독립적인 라이브러리로 빌드하고, 이들을 조합하는 논리적 연결망만 XML에 기술함으로써 코드의 재사용성을 높인다.

9.2 XML 문서의 핵심 계층 구조

행동 트리 XML은 정해진 스키마(Schema) 규칙을 엄격히 따른다.

  • <root> 엘리먼트: 문서의 최상위 태그로, 하나 이상의 BehaviorTree를 포함한다. main_tree_to_execute 속성을 통해 프로그램 실행 시 최우선으로 진입할 루트 트리를 지정한다.
  • <BehaviorTree> 엘리먼트: 고유한 ID를 가지며, 실제 노드들이 배치되는 논리적 공간이다. 단 하나의 최상위 노드(주로 제어 노드)만을 자식으로 가질 수 있다.
  • 노드 태그: <Sequence>, <Fallback>, <Inverter> 등의 내장 제어/데코레이터 노드와 시스템에 등록된 커스텀 액션 및 조건 노드가 태그 형태로 사용된다.

9.3 데이터 흐름 제어: 포트 리매핑(Port Remapping)

XML 내에서 노드의 속성(Attribute)은 블랙보드(Blackboard)를 통한 데이터 입출력 포트로 기능한다. 이때 중괄호 {} 문법을 사용하여 블랙보드의 특정 키(Key)를 포인터처럼 참조한다.

  • 상수 값 직접 할당: <Action ID="Wait" wait_duration="5"/> (블랙보드를 거치지 않고 직접 값을 전달함)
  • 블랙보드 포인터 참조: <Action ID="NavigateToPose" goal="{target_pose}"/> (블랙보드 메모리 영역 중 target_pose라는 키값을 가진 데이터를 읽어옴)

9.4 서브트리(SubTree)를 통한 모듈화

시스템이 거대해질 경우, 단일 XML 파일 내에 모든 트리를 명세하는 것은 유지보수성을 저하시킨다. 이를 방지하기 위해 특정 기능 단위로 트리를 분리하고 <SubTree> 태그를 통해 함수 호출(Function Call)처럼 재사용한다.

9.5 XML 명세의 학술적 예시 (Nav2 기반 최소 구조)

다음은 로봇이 특정 좌표로 이동하는 과정을 명세한 표준적인 행동 트리 XML의 예시다.

<root main_tree_to_execute="MainTree">
    <BehaviorTree ID="MainTree">
        <Sequence name="Navigate_with_Recovery">
            
            <Condition ID="IsBatteryOK" min_voltage="11.5"/>
            
            <Fallback name="Navigate_or_Recover">
                <SubTree ID="NavigateToWaypoint" target_goal="{goal_pose}"/>
                
                <Action ID="Spin" spin_dist="1.57" time_allowance="10.0"/>
            </Fallback>
            
        </Sequence>
    </BehaviorTree>

    <BehaviorTree ID="NavigateToWaypoint">
        <Sequence>
            <Action ID="ComputePathToPose" goal="{target_goal}" path="{global_path}"/>
            <Action ID="FollowPath" path="{global_path}"/>
        </Sequence>
    </BehaviorTree>
</root>

10. 내장 BT 노드

BehaviorTree.CPP 라이브러리는 행동 트리 아키텍처의 설계 편의성과 표준화를 위해 C++ 클래스로 사전 구현된 다양한 내장 노드(Built-in Nodes)를 제공한다. 이들은 별도의 코드 작성 없이 XML 트리 정의 파일에서 즉각적으로 호출하여 사용할 수 있으며, 그 목적과 논리 연산 구조에 따라 제어, 데코레이터, 유틸리티 노드로 분류된다.

사전 정의된 노드들의 공식 명칭과 학술적 동작 원리는 다음과 같다.

10.1 제어 노드 (Control Nodes)

하나 이상의 자식 노드를 관리하며, 하위 노드의 반환 상태에 따라 틱(Tick) 신호의 라우팅(Routing)을 결정한다.

  • Sequence (순차 노드)

자식 노드들을 왼쪽에서 오른쪽으로 순차적으로 틱한다. 하위 노드가 SUCCESS를 반환하면 다음 노드로 틱을 넘긴다. 단 하나라도 FAILURE를 반환하면 즉각 연산을 중단하고 상위로 FAILURE를 반환한다. RUNNING을 반환하는 노드를 만나면 상태를 기억하고 다음 주기에 해당 노드부터 다시 틱을 시작한다.

  • ReactiveSequence (반응형 순차 노드)

Sequence와 유사하나, 비동기 처리에 대한 접근이 다르다. 자식 노드 중 하나가 RUNNING 상태이더라도, 다음 틱이 발생했을 때 **반드시 첫 번째 자식 노드부터 다시 평가(재검사)**를 수행한다. 이는 선행 조건(예: “장애물이 없는가?”)이 실행 도중에도 계속 유지되는지 실시간으로 모니터링해야 하는 동적 환경 제어에 필수적이다.

  • Fallback / Selector (선택 노드)

자식 노드들을 순차적으로 틱하며, FAILURE를 반환할 때만 다음 자식 노드로 틱을 넘긴다. 단 하나라도 SUCCESS를 반환하면 즉시 상위로 SUCCESS를 반환하고 종료한다. 모든 자식이 FAILURE를 반환해야 최종적으로 FAILURE를 반환한다.

  • ReactiveFallback (반응형 선택 노드)

Fallback과 동일한 논리합(OR) 구조를 가지나, 다음 틱 발생 시 항상 첫 번째 자식 노드부터 다시 평가한다. 이를 통해 실행 중단 없이 우선순위가 더 높은 대체 행동으로 즉각적인 컨텍스트 스위칭(Context Switching)이 가능하다.

  • Parallel (병렬 노드)

연결된 모든 자식 노드에 동시에 틱을 전파한다. (물리적 멀티스레딩이 아닌 단일 스레드 내 순차적 빠른 호출로 구현됨). 성공 임계값(Threshold, 예: N개의 자식 중 M개 이상 성공)을 사전에 설정하여, 해당 조건을 충족하면 SUCCESS를 반환한다.

  • IfThenElse (조건 분기 노드)

정확히 3개의 자식 노드를 갖는다. 첫 번째 자식(조건 노드)을 먼저 틱하여, 결과가 SUCCESS이면 두 번째 자식(Then)을, FAILURE이면 세 번째 자식(Else)을 틱하는 전통적인 제어 흐름 분기를 수행한다.

10.2 데코레이터 노드 (Decorator Nodes)

단 하나의 자식 노드만 허용하며, 자식의 반환 상태를 변형하거나 틱의 실행 주기를 조작하는 필터 역할을 한다.

  • Inverter (반전 노드)

논리 부정(NOT) 연산자이다. 자식 노드의 SUCCESSFAILURE로, FAILURESUCCESS로 역전시켜 반환한다. 단, RUNNING 상태는 변형 없이 그대로 상위 노드로 전달한다.

  • ForceSuccess / ForceFailure (강제 상태 반환 노드)

자식 노드의 실제 연산 결과(FAILURE 또는 SUCCESS)와 무관하게, 항상 지정된 상태(SUCCESS 또는 FAILURE)로 마스킹하여 상위 노드에 반환한다.

  • Repeat (반복 노드)

자식 노드가 SUCCESS를 반환할 때마다 지정된 횟수(N)만큼 틱을 다시 호출한다. N번 연속으로 SUCCESS를 달성해야 최종 SUCCESS를 반환하며, 중간에 FAILURE가 발생하면 즉시 반복을 중단하고 FAILURE를 반환한다.

  • RetryUntilSuccessful (재시도 노드)

네트워크 지연이나 일시적 센서 오류 등 예외 상황 처리를 위한 노드이다. 자식 노드가 FAILURE를 반환하면, 성공할 때까지 또는 지정된 최대 재시도 횟수(Max Attempts)에 도달할 때까지 틱을 다시 발생시킨다.

  • Timeout (타임아웃 노드)

자식 노드가 지정된 시간(밀리초 단위) 내에 연산을 완료하지 못하고 RUNNING 상태를 초과 유지할 경우, 자식 노드의 실행을 강제 중단(Halt)시키고 FAILURE를 반환한다.

10.3 유틸리티 및 리프 노드 (Utility & Leaf Nodes)

트리 구조 내부에서 데이터 조작이나 단순 상태 반환, 대기 등을 수행하는 터미널 노드들이다.

  • AlwaysSuccess / AlwaysFailure

아무런 연산을 수행하지 않고 틱을 수신하는 즉시 각각 SUCCESSFAILURE를 반환한다. 전체 아키텍처를 설계할 때 아직 구현되지 않은 모듈의 자리를 채우는 디버그 스텁(Debug Stub) 용도로 사용된다.

  • SetBlackboard

정적 변수 할당을 위한 노드이다. 트리 실행 흐름이 이 노드에 도달하면, 사용자가 XML에서 지정한 특정 키(Key)와 값(Value)을 시스템의 블랙보드 메모리 영역에 기록한다.

  • Delay (지연 노드)

설정된 시간 동안 상위 노드에 RUNNING 상태를 지속적으로 반환하며 대기한다. 타이머가 만료된 직후의 틱에서 SUCCESS를 반환한다.

11. BT 노드 구현 방법론: 클래스 상속과 람다(Lambda) 기반 래핑(Wrapping)

특정 센서의 상태 변화를 평가하여 트리의 흐름을 제어하기 위해 반드시 BT::ConditionNode와 같은 기본 클래스를 명시적으로 상속(Inheritance)받아 새로운 C++ 클래스를 구현해야 하는 것은 아니다. BehaviorTree.CPP 프레임워크는 소프트웨어 설계의 유연성을 확보하기 위해 클래스 상속 방식 외에도 함수 포인터나 람다 함수(Lambda Expression)를 활용한 ‘단순 노드(Simple Node)’ 등록 메커니즘을 지원한다.

11.1 클래스 상속(Class Inheritance) 기반 구현

객체 지향 프로그래밍(OOP) 패러다임에 기반하여 BT::ConditionNode 또는 BT::SyncActionNode를 상속받아 새로운 클래스를 정의하는 전통적인 방식이다.

  • 구현 메커니즘: 파생 클래스(Derived Class)에서 가상 함수인 tick()을 오버라이딩(Overriding)하여 센서 상태를 평가하고 SUCCESS 또는 FAILURE를 반환하도록 설계한다.
  • 학술적 이점: * 캡슐화(Encapsulation): ROS2 서브스크라이버(Subscriber) 핸들, 메시지 콜백 함수, 그리고 센서의 이전 상태값과 같은 복잡한 내부 상태를 클래스 멤버 변수로 안전하게 격리할 수 있다.
  • 인터페이스 명확성: providedPorts() 정적 메서드를 통해 해당 노드가 요구하는 블랙보드 입출력 포트를 명시적으로 정의하고 타입 안정성을 런타임 이전에 검증할 수 있다.

11.2 단순 노드(Simple Node) 기반 구현 (비상속 방식)

별도의 C++ 클래스 정의 단계를 생략하고, 런타임에 실행 가능한 콜백(Callback) 함수를 트리 팩토리(TreeFactory)에 직접 바인딩(Binding)하는 방식이다.

  • 구현 메커니즘: BT::BehaviorTreeFactory::registerSimpleCondition() 메서드를 사용하여, 센서 상태를 확인하여 BT::NodeStatus를 반환하는 전역 함수, 클래스 멤버 함수, 또는 람다 함수를 직접 등록한다.
  • 학술적 이점:
  • 보일러플레이트(Boilerplate) 코드 감소: 클래스 선언, 생성자 구현 등의 반복적인 코드 작성을 생략할 수 있어 개발 효율성이 증가한다.
  • 경량화: 기존 로봇 제어 시스템에 이미 구현되어 있는 단순한 Getter 함수(예: bool isObstacleDetected())를 행동 트리에 즉각적으로 통합할 때 유리하다.

11.3 구현 방식의 비교 및 아키텍처 설계 기준

센서 데이터의 처리 복잡도와 ROS2 미들웨어와의 결합도에 따라 적절한 구현 방식을 취사선택해야 한다.

비교 기준클래스 상속 (Custom Class)단순 노드 래핑 (Simple Node)
시스템 복잡도상대적으로 높음 (별도의 헤더 및 소스 파일 관리 필요)매우 낮음 (인라인 람다 함수로 구현 가능)
상태 유지 (Stateful)멤버 변수를 통한 독립적인 상태 및 버퍼 관리에 매우 용이함외부 변수 캡처(Capture)나 전역 상태에 의존해야 함
ROS2 결합도노드 내부에서 직접 ROS2 통신 객체 생성 및 관리 가능외부 ROS2 노드에서 수신한 단순 플래그를 확인하는 데 적합
블랙보드 포트 정의providedPorts()를 통한 엄격한 정의 및 검증함수 등록 시 인자로 포트 리스트를 전달할 수 있으나 유연성 제한

11.4 결론

단순히 외부에서 갱신된 boolean 플래그나 특정 변수값을 확인하여 흐름을 분기하는 목적이라면 상속 없이 람다 함수를 활용한 SimpleConditionNode 등록만으로도 충분히 학술적 요구사항과 기능적 목적을 달성할 수 있다. 그러나 특정 센서 데이터를 수신하기 위한 ROS2 서브스크라이버를 BT 노드 내부에 자체적으로 인스턴스화하고 복잡한 신호 처리를 수행해야 한다면 클래스 상속 방식이 아키텍처적으로 타당하다.

12. 커스텀 BT 노드 C++ 설계 및 구현 명세

행동 트리(Behavior Tree, 이하 BT)를 ROS2 시스템과 통합하기 위해서는 BehaviorTree.CPP 라이브러리가 제공하는 기본 클래스들을 상속받거나, 람다(Lambda) 함수를 활용하여 런타임에 노드를 동적으로 등록하는 방식을 취한다. 다음은 두 가지 주요 구현 방식에 대한 C++ 아키텍처 설계 명세이다.

12.1 상속 기반 구현: ROS2 서브스크라이버를 포함한 조건 노드 (Condition Node)

이 방식은 ROS2의 통신 자원(퍼블리셔, 서브스크라이버 등)을 BT 노드 내부에 캡슐화할 때 사용된다. BT::ConditionNode를 상속받아 센서 데이터를 구독하고, 해당 데이터를 기반으로 SUCCESS 또는 FAILURE를 반환하는 구조를 설계한다.

설계 특징:

  • 의존성 주입 (Dependency Injection): BT 노드는 자체적으로 ROS2 노드(프로세스)가 될 수 없으므로, 생성자를 통해 호스트 ROS2 노드의 포인터(rclcpp::Node::SharedPtr)를 주입받아야 한다.
  • 비동기 콜백 처리: 서브스크라이버 콜백 함수는 백그라운드에서 데이터를 갱신하며, tick() 함수는 호출 시점에 캐시된 최신 데이터를 평가하여 상태를 반환한다.
#include <behaviortree_cpp_v3/behavior_tree.h>
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/battery_state.hpp>

// BT::ConditionNode를 상속받는 커스텀 조건 노드 클래스
class CheckBatteryNode : public BT::ConditionNode
{
public:
    // 생성자: 이름, 설정(포트 정보 등), 그리고 ROS2 노드 포인터를 인자로 받는다.
    CheckBatteryNode(const std::string& name, 
                     const BT::NodeConfiguration& config,
                     rclcpp::Node::SharedPtr ros_node)
      : BT::ConditionNode(name, config), 
        ros_node_(ros_node), 
        is_battery_low_(false)
    {
        // ROS2 서브스크라이버 초기화
        battery_sub_ = ros_node_->create_subscription<sensor_msgs::msg::BatteryState>(
            "/battery_status", 10,
            std::bind(&CheckBatteryNode::batteryCallback, this, std::placeholders::_1));
    }

    // 블랙보드와 연동할 포트(Port)를 정의한다. (이 예제에서는 사용하지 않으나 인터페이스 규격상 명시)
    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<double>("min_voltage", "Minimum allowed voltage") };
    }

    // 트리가 이 노드를 틱(Tick)할 때 호출되는 오버라이드 함수
    BT::NodeStatus tick() override
    {
        // 최신 배터리 상태를 기반으로 논리 평가 수행
        if (is_battery_low_) {
            RCLCPP_WARN(ros_node_->get_logger(), "Battery is LOW. Returning FAILURE.");
            return BT::NodeStatus::FAILURE;
        }
        
        RCLCPP_INFO(ros_node_->get_logger(), "Battery is OK. Returning SUCCESS.");
        return BT::NodeStatus::SUCCESS;
    }

private:
    // ROS2 서브스크라이버 콜백 함수
    void batteryCallback(const sensor_msgs::msg::BatteryState::SharedPtr msg)
    {
        double min_voltage = 11.0; 
        
        // 블랙보드에서 설정값을 읽어올 수 있는 경우 덮어쓴다.
        getInput("min_voltage", min_voltage);

        if (msg->voltage < min_voltage) {
            is_battery_low_ = true;
        } else {
            is_battery_low_ = false;
        }
    }

    rclcpp::Node::SharedPtr ros_node_;
    rclcpp::Subscription<sensor_msgs::msg::BatteryState>::SharedPtr battery_sub_;
    bool is_battery_low_;
};

12.2 람다(Lambda) 기반 단순 구현: 블랙보드 포트 접근 연산

복잡한 상태 유지가 필요 없는 단순한 연산이나 블랙보드 데이터 접근의 경우, 무거운 클래스 상속 대신 BT::SimpleConditionNode 또는 BT::SimpleActionNode 메커니즘을 람다 함수와 결합하여 구현한다.

설계 특징:

  • 보일러플레이트(Boilerplate) 최소화: 클래스 선언과 메서드 오버라이드 과정을 생략하여 코드의 가독성을 높인다.
  • 직접적인 노드 컨텍스트 참조: 람다 함수의 매개변수로 BT::TreeNode& self를 전달받아, 해당 노드의 포트(블랙보드) 인터페이스에 직접 접근한다.
#include <behaviortree_cpp_v3/behavior_tree.h>
#include <behaviortree_cpp_v3/bt_factory.h>
#include <geometry_msgs/msg/pose_stamped.hpp>
#include <iostream>

// 노드 등록 및 람다 구현을 수행하는 래퍼 함수 예시
void registerLambdaNodes(BT::BehaviorTreeFactory& factory)
{
    // 1. 해당 람다 노드가 사용할 포트 목록 정의
    BT::PortsList ports = {
        BT::InputPort<geometry_msgs::msg::PoseStamped>("target_pose"),
        BT::OutputPort<bool>("is_valid")
    };

    // 2. 람다 함수 정의 및 팩토리에 등록
    // 람다의 시그니처는 반드시 'BT::NodeStatus(BT::TreeNode&)' 형태여야 한다.
    factory.registerSimpleCondition("CheckTargetValidity", 
        [](BT::TreeNode&%20self) -> BT::NodeStatus 
        {
            geometry_msgs::msg::PoseStamped target;
            
            // 블랙보드(InputPort)로부터 데이터를 읽어온다.
            // 데이터가 없거나 읽기 실패 시 FAILURE 반환
            if (!self.getInput("target_pose", target)) {
                std::cerr << "Missing required input [target_pose]" << std::endl;
                return BT::NodeStatus::FAILURE;
            }

            // 간단한 유효성 검사 로직 (예: z축 좌표가 0보다 작으면 무효)
            bool valid = (target.pose.position.z >= 0.0);
            
            // 검사 결과를 블랙보드(OutputPort)에 기록한다.
            self.setOutput("is_valid", valid);

            if (valid) {
                return BT::NodeStatus::SUCCESS;
            } else {
                return BT::NodeStatus::FAILURE;
            }
        }, 
        ports); // 포트 정보를 함께 등록
}

12.3 노드 등록 및 팩토리(Factory) 연동 메커니즘

작성된 커스텀 클래스 노드와 람다 노드는 반드시 BT::BehaviorTreeFactory 객체에 등록되어야 XML 파싱 단계에서 인스턴스화될 수 있다. ROS2 노드의 메인 함수 또는 실행 엔진 클래스에서 다음과 같이 결합된다.

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto ros_node = rclcpp::Node::make_shared("bt_executor_node");

    BT::BehaviorTreeFactory factory;

    // 1. 클래스 상속 기반 노드 등록 (ROS2 노드 포인터를 팩토리를 통해 주입)
    // NodeBuilder는 생성자에 추가 인자(ros_node)를 전달하기 위한 람다 래퍼이다.
    BT::NodeBuilder builder_check_battery = 
        [ros_node](const%20std::string&%20name,%20const%20BT::NodeConfiguration&%20config)
    {
        return std::make_unique<CheckBatteryNode>(name, config, ros_node);
    };
    
    factory.registerBuilder<CheckBatteryNode>("CheckBattery", builder_check_battery);

    // 2. 람다 기반 단순 노드 등록
    registerLambdaNodes(factory);

    // XML 파일 로드 및 트리 생성 (생략)
    // auto tree = factory.createTreeFromFile("my_tree.xml");

    // rclcpp::spin(ros_node) 및 tree.tickRoot() 루프 등 실행 로직 (생략)

    rclcpp::shutdown();
    return 0;
}

13. 행동 트리 시각화 및 디버깅 방법론 (Visualization and Debugging)

복잡한 계층 구조를 갖는 행동 트리 아키텍처에서 노드의 상태 전이와 데이터 흐름을 추적하는 것은 시스템의 신뢰성 검증에 필수적입니다. BehaviorTree.CPP 프레임워크는 외부 도구와의 연동을 통해 실시간 상태 모니터링 및 오프라인 로그 분석 기능을 제공합니다.

13.1 실시간 상태 모니터링 (Groot 연동)

행동 트리의 런타임 상태는 ZeroMQ 통신 프로토콜을 기반으로 외부 GUI 애플리케이션(Groot 또는 Groot2)에 퍼블리시(Publish)될 수 있습니다.

  • PublisherZMQ 객체: C++ 메인 프로세스에서 트리 인스턴스가 생성된 후, BT::PublisherZMQ 객체를 인스턴스화하여 트리에 연결합니다. 이를 통해 각 틱(Tick) 주기마다 노드의 상태(SUCCESS, FAILURE, RUNNING) 변화가 외부 포트(기본값: 1666, 1667)로 송출됩니다.
  • 동적 추적(Dynamic Tracking): 로봇이 실제 주행하거나 시뮬레이션되는 동안, 실행 중인 특정 서브트리나 액션 노드의 활성화 여부를 시각적으로 확인할 수 있어 비동기 제어 로직의 병목이나 논리적 교착 상태(Deadlock)를 식별하는 데 사용됩니다.

13.2 오프라인 로깅 및 분석 (Data Logging)

실시간 모니터링 외에도, 실험 데이터의 사후 분석을 위해 행동 트리의 실행 이력을 파일 형태로 직렬화하여 저장할 수 있습니다.

  • FileLogger: 트리의 상태 변화 내역을 텍스트 기반 로그로 기록합니다.
  • MinitraceLogger / SQLiteLogger: 노드의 실행 시간(Execution Time)과 전이 이력을 프로파일링(Profiling)하기 위한 바이너리 또는 데이터베이스 형태의 로깅을 지원합니다. 이를 통해 각 액션 노드가 RUNNING 상태에 머무는 평균 체류 시간 등 시스템 성능 평가를 위한 정량적 지표를 추출할 수 있습니다.

13.3 아키텍처 내 적용 명세 (C++ 구현)

디버깅 모듈은 시스템의 주 제어 흐름에 영향을 주지 않도록 독립적으로 초기화되어야 합니다.

#include <behaviortree_cpp_v3/behavior_tree.h>
#include <behaviortree_cpp_v3/loggers/bt_zmq_publisher.h>
#include <behaviortree_cpp_v3/loggers/bt_file_logger.h>

// 트리 인스턴스 생성 이후 디버거 연결
BT::Tree tree = factory.createTreeFromFile("nav_tree.xml");

// 1. 실시간 모니터링을 위한 ZeroMQ 퍼블리셔 연결
BT::PublisherZMQ publisher_zmq(tree);

// 2. 오프라인 분석을 위한 파일 로거 연결
BT::FileLogger logger_file(tree, "bt_trace_log.fbl");

// 트리 틱(Tick) 루프 실행
while (rclcpp::ok()) {
    tree.tickRoot();
    rclcpp::spin_some(ros_node);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

14. Nav2 아키텍처 기반 커스텀 행동 트리(BT) 제어기 플러그인 통합 명세

ROS2 Navigation2(Nav2) 스택에서 행동 트리(Behavior Tree)는 단순한 C++ 애플리케이션의 일부가 아니라, bt_navigator 서버에 의해 런타임(Runtime)에 동적으로 로드(Dynamic Load)되는 플러그인(Plugin) 아키텍처를 기반으로 동작한다. 로봇의 하위 제어기(Controller)를 호출하거나 복잡한 커스텀 액션을 수행하기 위해서는, Nav2가 제공하는 전용 템플릿 클래스를 상속받아 플러그인 형태로 구현해야 한다.

14.1 Nav2 플러그인 시스템(Pluginlib) 기반 동적 로딩 메커니즘

bt_navigator는 메인 실행 컨텍스트에서 하드코딩된 노드 팩토리(Factory) 등록 방식을 배제하고, ROS2의 pluginlib 메커니즘을 채택한다.

이를 통해 개발자는 기존 시스템의 소스 코드를 수정하거나 재컴파일할 필요 없이, 공유 라이브러리(.so)로 빌드된 커스텀 BT 노드를 설정 파일(YAML)의 파라미터 수정을 통해 시스템에 주입(Injection)할 수 있다. 이는 제어 로직의 확장성(Extensibility)과 소프트웨어 결합도(Coupling)를 낮추는 핵심 설계 지침이다.

14.2 Nav2 전용 기저 클래스 (nav2_behavior_tree)

Nav2 생태계는 ROS2의 Action 및 Service 통신 패턴을 행동 트리와 수학적, 논리적으로 완벽히 결합하기 위해 nav2_behavior_tree 패키지 내부에 고도의 캡슐화가 적용된 템플릿 기저 클래스를 제공한다.

  • nav2_behavior_tree::BtActionNode<ActionT>: ROS2 액션 클라이언트(Action Client)를 내장한 비동기 액션 노드이다. 목표(Goal) 전송, 서버의 피드백(Feedback) 수신, 결과 대기 로직이 캡슐화되어 있다. 연산이 진행되는 동안 상태 기계(State Machine)에 따라 RUNNING을 반환하며, on_tick(), on_success(), on_aborted() 등의 가상 함수를 통해 수명 주기(Lifecycle)를 제어한다.
  • nav2_behavior_tree::BtServiceNode<ServiceT>: ROS2 서비스 클라이언트(Service Client)를 내장한 동기/비동기 하이브리드 노드이다. 서비스 요청을 보내고 응답을 대기하는 로직을 처리한다.

14.3 커스텀 제어기 BT 노드 C++ 구현 명세

로봇의 특정 궤적 제어기(예: FollowPath 액션)를 호출하는 커스텀 액션 노드의 구현은 다음과 같이 BtActionNode를 상속받아 구현된다. 내부 통신 핸들과 틱(Tick) 지연 처리는 부모 클래스가 위임받아 처리하므로, 개발자는 비즈니스 로직에만 집중할 수 있다.

#include <string>
#include <memory>
#include "nav2_behavior_tree/bt_action_node.hpp"
#include "nav2_msgs/action/follow_path.hpp"

namespace nav2_custom_nodes
{
// nav2_msgs::action::FollowPath 액션 타입을 템플릿 인자로 전달하여 상속
class CustomFollowPathAction : public nav2_behavior_tree::BtActionNode<nav2_msgs::action::FollowPath>
{
public:
    // 생성자: 부모 클래스의 생성자에 액션 서버 이름("follow_path")을 전달한다.
    CustomFollowPathAction(
        const std::string & xml_tag_name,
        const std::string & action_name,
        const BT::NodeConfiguration & conf)
    : BtActionNode<nav2_msgs::action::FollowPath>(xml_tag_name, action_name, conf)
    {
    }

    // 포트 명세: 블랙보드에서 제어기가 요구하는 '전역 경로(path)'를 입력받는다.
    static BT::PortsList providedPorts()
    {
        return providedBasicPorts({
            BT::InputPort<nav_msgs::msg::Path>("path", "Path to follow"),
            BT::InputPort<std::string>("controller_id", "Controller plugin to use")
        });
    }

    // 틱이 발생하여 액션 목표(Goal)를 서버로 전송하기 직전에 호출된다.
    void on_tick() override
    {
        nav_msgs::msg::Path path;
        std::string controller_id;

        // 블랙보드에서 목표 경로와 제어기 ID 추출
        getInput("path", path);
        getInput("controller_id", controller_id);

        // ActionT::Goal 객체에 데이터 세팅
        goal_.path = path;
        goal_.controller_id = controller_id;
    }

    // 액션 서버로부터 SUCCESS 결과를 수신했을 때 호출된다.
    BT::NodeStatus on_success() override
    {
        // 필요시 내부 상태 초기화 또는 블랙보드에 결과 기록
        return BT::NodeStatus::SUCCESS;
    }

    // 액션 서버로부터 ABORTED 결과를 수신했을 때 호출된다.
    BT::NodeStatus on_aborted() override
    {
        return BT::NodeStatus::FAILURE;
    }

    // 액션 서버로부터 CANCELLED 결과를 수신했을 때 호출된다.
    BT::NodeStatus on_cancelled() override
    {
        return BT::NodeStatus::SUCCESS; // 의도된 취소일 경우 SUCCESS 처리 (정책에 따라 다름)
    }
};

}  // namespace nav2_custom_nodes

// 플러그인 등록 매크로 (pluginlib 엑스포트)
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_custom_nodes::CustomFollowPathAction, BT::ActionNodeBase)

14.4 빌드 시스템 및 플러그인 설정 (CMakeLists.txt)

구현된 C++ 소스 코드를 ROS2 에코시스템 내에서 동적 라이브러리 플러그인으로 동작시키기 위해 빌드 명세서를 엄격하게 구성해야 한다. 공유 라이브러리 타겟을 생성하고 pluginlib에 등록한다.

# CMakeLists.txt 내 플러그인 빌드 설정
add_library(custom_bt_nodes SHARED
  src/custom_follow_path_action.cpp
)

ament_target_dependencies(custom_bt_nodes
  rclcpp
  behaviortree_cpp_v3
  nav2_behavior_tree
  nav2_msgs
  pluginlib
)

# 플러그인 내보내기 설정 파일(예: bt_plugins.xml) 지정
pluginlib_export_plugin_description_file(behaviortree_cpp_v3 custom_bt_plugins.xml)

install(TARGETS custom_bt_nodes
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION binCMake
)

14.5 파라미터 기반 플러그인 로드 (YAML Configuration)

컴파일이 완료된 라이브러리를 Nav2의 bt_navigator가 인지하고 메모리에 적재하도록 YAML 파라미터 파일의 plugin_lib_names 리스트에 플러그인 이름을 추가한다.

bt_navigator:
  ros__parameters:
    use_sim_time: true
    global_frame: map
    robot_base_frame: base_link
    # 플러그인 라이브러리 명시 (확장자 .so 생략)
    plugin_lib_names:
      - nav2_compute_path_to_pose_action_bt_node
      - nav2_follow_path_action_bt_node
      - custom_bt_nodes  # 여기서 개발자가 빌드한 커스텀 노드 라이브러리를 주입한다.

15. 커스텀 행동 트리 노드의 단위 테스트(Unit Test) 및 검증(Validation) 방법론

로봇 제어 소프트웨어 공학에서 행동 트리(Behavior Tree) 노드의 단위 테스트는 개별 논리 모듈이 시스템의 나머지 부분(ROS2 미들웨어, 하드웨어 계층 등)과 격리된 상태에서 결정론적(Deterministic)으로 동작함을 수학적·논리적으로 증명하는 과정이다. 이는 통합 테스트(Integration Test) 이전에 상태 전이의 무결성과 메모리(블랙보드) 접근의 안정성을 검증하여 시스템 전체의 신뢰성을 확보하는 핵심 절차이다.

15.1 학술적 검증 목적 및 테스트 범위

행동 트리 노드의 단위 테스트는 다음 세 가지 명제를 검증하는 데 초점을 맞춘다.

  • 상태 반환의 결정론(Determinism of State Return): 주어진 입력 조건(블랙보드 변수, 센서 모의 데이터)에 대해 노드가 SUCCESS, FAILURE, RUNNING 중 설계된 상태를 정확히 반환하는가.
  • 데이터 입출력 무결성(Data I/O Integrity): 노드가 블랙보드의 지정된 포트(Port)에서 예상된 타입(Type)의 데이터를 안전하게 읽고(getInput), 연산 결과를 정확히 기록(setOutput)하는가. 누락된 데이터에 대해 예외(Exception)를 적절히 처리하거나 FAILURE로 폴백(Fallback)하는가.
  • 수명 주기(Lifecycle) 준수: 비동기 액션 노드의 경우 on_start(), on_tick(), on_halt() 등의 가상 함수가 제어 흐름에 따라 정확한 순서로 호출되며 자원 누수(Memory Leak) 없이 종료되는가.

15.2 테스트 프레임워크 및 환경 구축

ROS2 생태계의 표준 테스트 프레임워크인 **GTest (Google Test)**와 모의 객체 생성을 위한 **GMock (Google Mock)**을 BehaviorTree.CPP 라이브러리와 결합하여 테스트 환경을 구축한다.

빌드 시스템(CMakeLists.txt) 상에서는 ament_add_gtest 매크로를 사용하여 컴파일 타임에 독립된 테스트 실행 파일을 생성한다.

# CMakeLists.txt 내 단위 테스트 빌드 명세
if(BUILD_TESTING)
  find_package(ament_cmake_gtest REQUIRED)
  
  ament_add_gtest(test_custom_bt_nodes test/test_custom_nodes.cpp)
  target_link_libraries(test_custom_bt_nodes
    custom_bt_nodes # 테스트 대상 라이브러리
    behaviortree_cpp_v3
    rclcpp
  )
endif()

15.3 블랙보드(Blackboard) 격리 및 포트 연산 테스트

ROS2 통신이 필요 없는 단순 논리 연산 노드나 데이터 변환 노드는 별도의 트리 팩토리(Factory)를 거치지 않고, 노드 객체를 직접 인스턴스화하여 블랙보드와의 상호작용만을 격리 테스트한다.

  • 독립 인스턴스화: 빈 블랙보드 객체를 생성하고, 노드의 초기화 매개변수로 주입한다.
  • 경계값 테스트(Boundary Value Testing): 유효하지 않은 포트 매핑이나 타입 불일치(Type Mismatch) 상황을 인위적으로 조성하여 시스템이 크래시(Crash)되지 않고 안정적으로 실패 상태(FAILURE)를 반환하는지 검증한다.

15.4 비동기 통신 종속성 제거: 모킹(Mocking) 기법

Nav2의 BtActionNode를 상속받은 노드 등 ROS2의 Action/Service 클라이언트를 내장한 커스텀 노드를 테스트할 때는 실제 서버 노드를 실행하지 않는다. 이는 단위 테스트의 독립성 원칙에 위배되기 때문이다.

  • 의존성 주입 역전(Dependency Injection Inversion): 실제 ROS2 네트워크 레이어 대신 메모리 상에서만 동작하는 더미 서버(Dummy Action Server)를 띄우거나, 가상 함수 오버라이딩을 통해 네트워크 호출부를 모킹(Mocking)한다.
  • 상태 주입(State Injection): 서버로부터 SUCCESS, ABORTED, CANCELLED 응답이 반환되는 상황을 강제로 모의(Simulate)하여, BT 노드가 각 상황에 맞게 올바른 BT::NodeStatus를 트리의 상위 계층으로 전달하는지 검증한다.

15.5 C++ 단위 테스트 구현 명세 (GTest)

다음은 블랙보드에서 목표 좌표를 읽어와 유효성을 검사하는 커스텀 조건 노드(CheckTargetValidity)에 대한 GTest 기반 검증 코드의 학술적 예시이다.

#include <gtest/gtest.h>
#include <behaviortree_cpp_v3/behavior_tree.h>
#include <geometry_msgs/msg/pose_stamped.hpp>
// 검증 대상 헤더 포함
#include "my_custom_nodes/check_target_validity.hpp" 

class CustomNodeTest : public ::testing::Test
{
protected:
    void SetUp() override
    {
        // 격리된 테스트 환경을 위한 전역 블랙보드 초기화
        blackboard_ = BT::Blackboard::create();
        
        // 노드 구성 객체(Configuration) 생성
        BT::NodeConfiguration config;
        config.blackboard = blackboard_;
        
        // 단위 테스트 대상 노드 직접 인스턴스화 (XML 파싱 생략)
        node_ = std::make_shared<CheckTargetValidity>("CheckValidity", config);
    }

    BT::Blackboard::Ptr blackboard_;
    std::shared_ptr<CheckTargetValidity> node_;
};

// 테스트 케이스 1: 정상적인 데이터 입력 시 SUCCESS 반환 검증
TEST_F(CustomNodeTest, ValidTargetReturnsSuccess)
{
    geometry_msgs::msg::PoseStamped valid_pose;
    valid_pose.pose.position.z = 1.0; // 유효한 Z축 조건 (> 0.0)

    // 블랙보드에 모의 데이터 주입
    blackboard_->set("target_pose", valid_pose);

    // 노드 단일 틱 실행 및 상태 반환 무결성 검증
    BT::NodeStatus status = node_->executeTick();
    EXPECT_EQ(status, BT::NodeStatus::SUCCESS);
}

// 테스트 케이스 2: 블랙보드 데이터 누락(Missing Data) 시 예외 처리 및 FAILURE 반환 검증
TEST_F(CustomNodeTest, MissingDataReturnsFailure)
{
    // target_pose를 블랙보드에 설정하지 않은 상태로 틱 실행
    BT::NodeStatus status = node_->executeTick();
    EXPECT_EQ(status, BT::NodeStatus::FAILURE);
}

// 테스트 케이스 3: 조건 불충족 시 FAILURE 반환 검증
TEST_F(CustomNodeTest, InvalidTargetReturnsFailure)
{
    geometry_msgs::msg::PoseStamped invalid_pose;
    invalid_pose.pose.position.z = -1.0; // 유효하지 않은 Z축 조건

    blackboard_->set("target_pose", invalid_pose);

    BT::NodeStatus status = node_->executeTick();
    EXPECT_EQ(status, BT::NodeStatus::FAILURE);
}

int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

16. 행동 트리 기반 폴백(Fallback) 및 복구(Recovery) 아키텍처 설계

로봇 자율 주행 시스템에서 예기치 않은 하드웨어 오류, 센서 노이즈, 또는 동적 장애물에 의한 경로 폐쇄 등은 필연적으로 발생한다. 행동 트리(Behavior Tree) 아키텍처는 이러한 예외 상황(Exception)을 처리하고 시스템의 결함 허용성(Fault Tolerance)을 확보하기 위해 계층적 폴백(Fallback) 메커니즘을 핵심 기능으로 제공한다.

16.1 복구 제어 흐름의 학술적 원리 및 구조적 이점

유한 상태 기계(FSM) 기반 시스템의 경우, 예외 처리를 위해 모든 정상 작동 상태에서 예외 처리 상태로 향하는 전역 전이(Global Transition) 조건을 명시해야 하므로 시스템 복잡도가 기하급수적으로 증가하는 상태 폭발(State Explosion) 문제에 직면한다.

반면, 행동 트리는 제어 노드의 우선순위 기반 하향식 라우팅 구조를 통해 이 문제를 해결한다. Fallback (또는 Selector) 노드는 자식 노드들을 왼쪽에서 오른쪽으로 평가하며, 선행 노드가 FAILURE를 반환했을 때만 다음 자식 노드로 틱(Tick)을 전파하는 논리합(OR) 연산을 수행한다. 이를 통해 기본 임무(Primary Task)와 실패 시 실행할 대체 임무(Recovery Action)를 구조적으로 분리하여 높은 모듈성을 유지할 수 있다.

16.2 복구를 위한 특수 제어 노드 (Nav2 아키텍처 중심)

ROS2 Nav2 생태계에서는 기본적인 Fallback 노드 외에도 로봇 주행의 특성을 반영한 특수한 복구 전용 제어 노드를 제공한다.

  • RecoveryNode (복구 노드): 정확히 두 개의 자식 노드(1번: 주 임무 노드, 2번: 복구 액션 노드)만을 허용하는 데코레이터 성격의 제어 노드이다. 주 임무 노드가 실패(FAILURE)할 경우, 즉시 전체 시스템을 실패로 간주하지 않고 2번 복구 액션 노드를 틱(Tick)한다. 복구 액션이 성공(SUCCESS)적으로 완료되면, 다시 주 임무 노드를 재시도(Retry)한다. 이 과정은 XML 파라미터로 지정된 최대 재시도 횟수(number_of_retries)만큼 반복된다.
  • ReactiveFallback (반응형 선택 노드): 복구 행동을 수행하여 RUNNING 상태를 반환하고 있는 도중에도, 매 주기마다 트리 루트에서 발생한 틱이 첫 번째 자식 노드(주 임무 또는 선행 조건)를 재평가한다. 만약 동적 장애물이 사라져 첫 번째 자식 노드의 조건이 다시 충족(SUCCESS)되면, 진행 중이던 복구 행동을 즉각 강제 중단(Preempt/Halt)시키고 본래의 임무 제어 흐름으로 복귀한다.

16.3 복구 전략의 계층적 분류 (Hierarchical Recovery Strategy)

학술적 관점에서 복구 행동은 발생한 오류의 범위와 심각도에 따라 두 가지 계층으로 분리하여 설계된다.

  • 지역적 복구 (Local Recovery): 특정 서브트리나 하위 액션 노드의 실패에 국한된 대응 방식이다. 예를 들어, ComputePathToPose (전역 경로 탐색) 노드가 경로 생성에 실패할 경우, 해당 서브트리 내에서만 ClearCostmap (코스트맵 초기화) 액션을 수행한 뒤 경로 탐색을 재시도한다. 다른 시스템(예: 배터리 관리, 비전 인식)의 논리 흐름에는 영향을 주지 않는다.
  • 전역적 복구 (Global Recovery): 시스템 전체의 임무 수행이 불가능한 교착 상태(Deadlock)에 빠졌을 때 발동된다. 모든 지역적 복구(예: 코스트맵 초기화, 제자리 회전 Spin, 후진 BackUp)가 실패 횟수를 초과하여 최종적으로 최상위 트리에 FAILURE가 전달되었을 때 실행된다. 주로 운영자에게 알림을 송출하거나(Wait for Help) 안전 모드(Safe Mode)로 진입하는 논리로 구성된다.

16.4 복구 아키텍처 XML 명세 예시 (Nav2)

다음은 목표 지점으로의 이동이 실패했을 때, 코스트맵 초기화와 제자리 회전을 통한 공간 확보를 순차적으로 시도하는 전형적인 지역적 복구 로직의 XML 구조이다.

<root main_tree_to_execute="MainTree">
    <BehaviorTree ID="MainTree">
        <RecoveryNode number_of_retries="3" name="NavigateRecovery">
            
            <Sequence name="Navigate">
                <Action ID="ComputePathToPose" goal="{goal}" path="{path}"/>
                <Action ID="FollowPath" path="{path}"/>
            </Sequence>

            <Sequence name="RecoveryActions">
                <Action ID="ClearEntireCostmap" name="ClearGlobalCostmap" service_name="global_costmap/clear_entirely_global_costmap"/>
                <Action ID="ClearEntireCostmap" name="ClearLocalCostmap" service_name="local_costmap/clear_entirely_local_costmap"/>
                
                <Action ID="Spin" spin_dist="1.57" time_allowance="10.0"/>
            </Sequence>
            
        </RecoveryNode>
    </BehaviorTree>
</root>

제시한 요구사항에 따라 작성한 C++ 코드 수준의 설계 및 구현 명세이다.


17. ROS2 환경에서의 행동 트리(Behavior Tree) 커스텀 노드 C++ 설계 및 구현 명세

행동 트리(Behavior Tree, 이하 BT)를 ROS2 시스템과 통합하기 위해서는 BehaviorTree.CPP 라이브러리가 제공하는 기본 클래스들을 상속받거나, 람다(Lambda) 함수를 활용하여 런타임에 노드를 동적으로 등록하는 방식을 취한다. 다음은 두 가지 주요 구현 방식에 대한 C++ 아키텍처 설계 명세이다.

17.1 상속 기반 구현: ROS2 서브스크라이버를 포함한 조건 노드 (Condition Node)

이 방식은 ROS2의 통신 자원(퍼블리셔, 서브스크라이버 등)을 BT 노드 내부에 캡슐화할 때 사용된다. BT::ConditionNode를 상속받아 센서 데이터를 구독하고, 해당 데이터를 기반으로 SUCCESS 또는 FAILURE를 반환하는 구조를 설계한다.

설계 특징:

  • 의존성 주입 (Dependency Injection): BT 노드는 자체적으로 ROS2 노드(프로세스)가 될 수 없으므로, 생성자를 통해 호스트 ROS2 노드의 포인터(rclcpp::Node::SharedPtr)를 주입받아야 한다.
  • 비동기 콜백 처리: 서브스크라이버 콜백 함수는 백그라운드에서 데이터를 갱신하며, tick() 함수는 호출 시점에 캐시된 최신 데이터를 평가하여 상태를 반환한다.
#include <behaviortree_cpp_v3/behavior_tree.h>
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/battery_state.hpp>

// BT::ConditionNode를 상속받는 커스텀 조건 노드 클래스
class CheckBatteryNode : public BT::ConditionNode
{
public:
    // 생성자: 이름, 설정(포트 정보 등), 그리고 ROS2 노드 포인터를 인자로 받는다.
    CheckBatteryNode(const std::string& name, 
                     const BT::NodeConfiguration& config,
                     rclcpp::Node::SharedPtr ros_node)
      : BT::ConditionNode(name, config), 
        ros_node_(ros_node), 
        is_battery_low_(false)
    {
        // ROS2 서브스크라이버 초기화
        battery_sub_ = ros_node_->create_subscription<sensor_msgs::msg::BatteryState>(
            "/battery_status", 10,
            std::bind(&CheckBatteryNode::batteryCallback, this, std::placeholders::_1));
    }

    // 블랙보드와 연동할 포트(Port)를 정의한다. (이 예제에서는 사용하지 않으나 인터페이스 규격상 명시)
    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<double>("min_voltage", "Minimum allowed voltage") };
    }

    // 트리가 이 노드를 틱(Tick)할 때 호출되는 오버라이드 함수
    BT::NodeStatus tick() override
    {
        // 최신 배터리 상태를 기반으로 논리 평가 수행
        if (is_battery_low_) {
            RCLCPP_WARN(ros_node_->get_logger(), "Battery is LOW. Returning FAILURE.");
            return BT::NodeStatus::FAILURE;
        }
        
        RCLCPP_INFO(ros_node_->get_logger(), "Battery is OK. Returning SUCCESS.");
        return BT::NodeStatus::SUCCESS;
    }

private:
    // ROS2 서브스크라이버 콜백 함수
    void batteryCallback(const sensor_msgs::msg::BatteryState::SharedPtr msg)
    {
        double min_voltage = 11.0; 
        
        // 블랙보드에서 설정값을 읽어올 수 있는 경우 덮어쓴다.
        getInput("min_voltage", min_voltage);

        if (msg->voltage < min_voltage) {
            is_battery_low_ = true;
        } else {
            is_battery_low_ = false;
        }
    }

    rclcpp::Node::SharedPtr ros_node_;
    rclcpp::Subscription<sensor_msgs::msg::BatteryState>::SharedPtr battery_sub_;
    bool is_battery_low_;
};

17.2 람다(Lambda) 기반 단순 구현: 블랙보드 포트 접근 연산

복잡한 상태 유지가 필요 없는 단순한 연산이나 블랙보드 데이터 접근의 경우, 무거운 클래스 상속 대신 BT::SimpleConditionNode 또는 BT::SimpleActionNode 메커니즘을 람다 함수와 결합하여 구현한다.

설계 특징:

  • 보일러플레이트(Boilerplate) 최소화: 클래스 선언과 메서드 오버라이드 과정을 생략하여 코드의 가독성을 높인다.
  • 직접적인 노드 컨텍스트 참조: 람다 함수의 매개변수로 BT::TreeNode& self를 전달받아, 해당 노드의 포트(블랙보드) 인터페이스에 직접 접근한다.
#include <behaviortree_cpp_v3/behavior_tree.h>
#include <behaviortree_cpp_v3/bt_factory.h>
#include <geometry_msgs/msg/pose_stamped.hpp>
#include <iostream>

// 노드 등록 및 람다 구현을 수행하는 래퍼 함수 예시
void registerLambdaNodes(BT::BehaviorTreeFactory& factory)
{
    // 1. 해당 람다 노드가 사용할 포트 목록 정의
    BT::PortsList ports = {
        BT::InputPort<geometry_msgs::msg::PoseStamped>("target_pose"),
        BT::OutputPort<bool>("is_valid")
    };

    // 2. 람다 함수 정의 및 팩토리에 등록
    // 람다의 시그니처는 반드시 'BT::NodeStatus(BT::TreeNode&)' 형태여야 한다.
    factory.registerSimpleCondition("CheckTargetValidity", 
        [](BT::TreeNode&%20self) -> BT::NodeStatus 
        {
            geometry_msgs::msg::PoseStamped target;
            
            // 블랙보드(InputPort)로부터 데이터를 읽어온다.
            // 데이터가 없거나 읽기 실패 시 FAILURE 반환
            if (!self.getInput("target_pose", target)) {
                std::cerr << "Missing required input [target_pose]" << std::endl;
                return BT::NodeStatus::FAILURE;
            }

            // 간단한 유효성 검사 로직 (예: z축 좌표가 0보다 작으면 무효)
            bool valid = (target.pose.position.z >= 0.0);
            
            // 검사 결과를 블랙보드(OutputPort)에 기록한다.
            self.setOutput("is_valid", valid);

            if (valid) {
                return BT::NodeStatus::SUCCESS;
            } else {
                return BT::NodeStatus::FAILURE;
            }
        }, 
        ports); // 포트 정보를 함께 등록
}

17.3 노드 등록 및 팩토리(Factory) 연동 메커니즘

작성된 커스텀 클래스 노드와 람다 노드는 반드시 BT::BehaviorTreeFactory 객체에 등록되어야 XML 파싱 단계에서 인스턴스화될 수 있다. ROS2 노드의 메인 함수 또는 실행 엔진 클래스에서 다음과 같이 결합된다.

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto ros_node = rclcpp::Node::make_shared("bt_executor_node");

    BT::BehaviorTreeFactory factory;

    // 1. 클래스 상속 기반 노드 등록 (ROS2 노드 포인터를 팩토리를 통해 주입)
    // NodeBuilder는 생성자에 추가 인자(ros_node)를 전달하기 위한 람다 래퍼이다.
    BT::NodeBuilder builder_check_battery = 
        [ros_node](const%20std::string&%20name,%20const%20BT::NodeConfiguration&%20config)
    {
        return std::make_unique<CheckBatteryNode>(name, config, ros_node);
    };
    
    factory.registerBuilder<CheckBatteryNode>("CheckBattery", builder_check_battery);

    // 2. 람다 기반 단순 노드 등록
    registerLambdaNodes(factory);

    // XML 파일 로드 및 트리 생성 (생략)
    // auto tree = factory.createTreeFromFile("my_tree.xml");

    // rclcpp::spin(ros_node) 및 tree.tickRoot() 루프 등 실행 로직 (생략)

    rclcpp::shutdown();
    return 0;
}

18. BT::StatefulActionNode와 ROS2 Action Client 통합 설계 명세

BT::StatefulActionNode와 ROS2 액션 클라이언트(Action Client)를 통합하는 방식은 로봇이 이동(Navigation)이나 매니퓰레이션(Manipulation)과 같이 시간이 소요되는 작업을 수행할 때 필수적이다. 이는 단일 틱(Tick) 주기에 작업을 완료하지 않고, 작업이 진행 중인 동안 RUNNING 상태를 유지하며 메인 루프를 차단하지 않는 비동기 구조를 취한다.

BT::StatefulActionNode는 상태 변화를 관리하기 위해 세 가지 핵심 가상 함수(onStart, onRunning, onHalted)를 제공한다. ROS2 액션의 생명주기와 이를 매핑하여 설계한다.

18.1 주요 메서드 설계 및 역할

  • onStart(): 트리가 해당 노드를 처음 실행하거나, 이전에 SUCCESS/FAILURE로 종료된 후 다시 실행될 때 단 한 번 호출된다. 이 시점에 ROS2 액션 목표(Goal)를 서버로 전송하고, 비동기 처리를 위한 초기화를 수행한다. RUNNING을 반환하여 실행이 시작되었음을 알린다.
  • onRunning(): 노드가 RUNNING 상태인 동안 매 틱마다 반복 호출된다. 액션 서버로부터 결과(Result)가 도착했는지 확인한다. 결과가 도착하기 전까지는 계속 RUNNING을 반환하며, 결과에 따라 SUCCESS 또는 FAILURE를 반환하며 작업을 종료한다.
  • onHalted(): 상위 노드(예: Parallel 노드의 다른 자식 실패 또는 Selector의 조건 변화)에 의해 현재 작업이 강제로 중단될 때 호출된다. 이 메서드 내에서 ROS2 액션 목표를 취소(Cancel)하여 로봇의 물리적 동작을 안전하게 정지시켜야 한다.

18.2 C++ 구현 예시: 로봇 이동 액션 노드

다음은 nav2_msgs::action::NavigateToPose를 호출하는 커스텀 액션 노드의 구현 예시이다.

#include <behaviortree_cpp_v3/action_node.h>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <nav2_msgs/action/navigate_to_pose.hpp>

class MoveBaseAction : public BT::StatefulActionNode
{
public:
    using NavigateToPose = nav2_msgs::action::NavigateToPose;
    using GoalHandleNav = rclcpp_action::ClientGoalHandle<NavigateToPose>;

    MoveBaseAction(const std::string& name, const BT::NodeConfiguration& config,
                   rclcpp::Node::SharedPtr ros_node)
        : BT::StatefulActionNode(name, config), ros_node_(ros_node)
    {
        // 액션 클라이언트 생성
        action_client_ = rclcpp_action::create_client<NavigateToPose>(ros_node_, "navigate_to_pose");
    }

    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<geometry_msgs::msg::PoseStamped>("goal_pose") };
    }

    // 작업 시작 시 호출 (최초 1회)
    BT::NodeStatus onStart() override
    {
        if (!action_client_->wait_for_action_server(std::chrono::seconds(1))) {
            RCLCPP_ERROR(ros_node_->get_logger(), "Action server not available");
            return BT::NodeStatus::FAILURE;
        }

        NavigateToPose::Goal goal;
        if (!getInput("goal_pose", goal.pose)) {
            RCLCPP_ERROR(ros_node_->get_logger(), "Missing [goal_pose] input");
            return BT::NodeStatus::FAILURE;
        }

        // 비동기 목표 전송 설정
        auto send_goal_options = rclcpp_action::Client<NavigateToPose>::SendGoalOptions();
        send_goal_options.result_callback = 
            [this](const%20GoalHandleNav::WrappedResult&%20result) {
                this->action_result_ = result;
                this->is_action_finished_ = true;
            };

        action_client_->async_send_goal(goal, send_goal_options);
        
        is_action_finished_ = false;
        RCLCPP_INFO(ros_node_->get_logger(), "Goal sent to Action Server");
        return BT::NodeStatus::RUNNING;
    }

    // 작업 진행 중 매 틱마다 호출
    BT::NodeStatus onRunning() override
    {
        if (!is_action_finished_) {
            return BT::NodeStatus::RUNNING;
        }

        // 결과에 따른 상태 반환
        switch (action_result_.code) {
            case rclcpp_action::ResultCode::SUCCEEDED:
                return BT::NodeStatus::SUCCESS;
            case rclcpp_action::ResultCode::ABORTED:
                return BT::NodeStatus::FAILURE;
            case rclcpp_action::ResultCode::CANCELED:
                return BT::NodeStatus::FAILURE;
            default:
                return BT::NodeStatus::FAILURE;
        }
    }

    // 외부 요인으로 인한 중단 시 호출
    void onHalted() override
    {
        RCLCPP_WARN(ros_node_->get_logger(), "Action halted. Cancelling goal.");
        action_client_->async_cancel_all_goals();
    }

private:
    rclcpp::Node::SharedPtr ros_node_;
    rclcpp_action::Client<NavigateToPose>::SharedPtr action_client_;
    
    bool is_action_finished_ = false;
    GoalHandleNav::WrappedResult action_result_;
};

18.3 설계 시 고려사항

  1. 스레드 안전성 (Thread Safety): ROS2 콜백 스레드와 BT 실행 스레드가 분리되어 동작하므로, is_action_finished_와 같은 플래그 변수는 원자적 연산이나 적절한 동기화가 필요할 수 있다. BehaviorTree.CPP의 기본 구조는 틱 실행 중에 상태를 안전하게 검사하도록 설계되어 있다.
  2. 타임아웃 처리: 액션 서버가 목표 수락을 거부하거나 응답이 없는 경우에 대비하여 onStart에서 적절한 예외 처리 로직을 포함해야 한다.
  3. 포트 리매핑 활용: providedPorts를 통해 정의된 goal_pose는 XML 파일에서 블랙보드의 특정 키와 연결되어, 런타임에 유연하게 목적지를 변경하며 동일한 C++ 노드 클래스를 재사용할 수 있게 한다.

위와 같은 구조를 통해 ROS2의 비동기 통신 모델을 행동 트리의 동기적 틱 메커니즘 내에 안전하게 통합할 수 있다. 이 명세는 학술적 정의에 충실하며, 실제 로봇 제어 시스템의 표준적인 구현 패턴을 따른다.

19. 복잡한 동적 환경에서의 센서 퓨전 및 컨텍스트 스위칭 행동 트리 설계 패턴

다중 센서 기반의 자율 주행 및 매니퓰레이션 로봇은 환경 변화에 즉각적으로 반응하여 실행 중인 작업을 중단하고 우선순위가 높은 다른 작업으로 전환(Context Switching)해야 한다. 행동 트리 아키텍처에서는 이를 ’반응형 제어 노드(Reactive Control Nodes)’와 ‘블랙보드 기반 센서 데이터 추상화’ 메커니즘을 결합하여 구현한다.

19.1 센서 퓨전 데이터의 추상화 및 블랙보드 통합

행동 트리는 논리적 제어 흐름을 담당하는 아키텍처이므로, 칼만 필터(Kalman Filter)나 파티클 필터(Particle Filter)와 같은 고연산 센서 퓨전 알고리즘을 BT 노드 내부에서 직접 수행하는 것은 안티 패턴(Anti-pattern)이다.

아키텍처 분리 및 추상화 원칙

  • ROS2 퍼셉션 노드(Perception Node): 라이다(LiDAR), 카메라, 초음파 센서 등의 원시 데이터(Raw Data)를 구독하고 융합 연산을 수행하는 독립적인 ROS2 노드를 구성한다.
  • 의미론적 추상화(Semantic Abstraction): 퍼셉션 노드는 융합된 데이터를 행동 트리가 이해할 수 있는 고수준의 상태 값(예: is_obstacle_imminent, human_detected_zone, confidence_level)으로 변환하여 토픽으로 발행한다.
  • 상태 동기화 노드(State Synchronization Node): 트리의 최상단 부분에 위치한 SimpleConditionNode 또는 백그라운드 스레드가 해당 토픽을 구독하여 최신 상태를 블랙보드(Blackboard)에 지속적으로 기록(Write)한다.

이러한 분리를 통해 트리는 복잡한 수학적 연산에서 해방되며, 오직 논리적 조건 평가에만 집중할 수 있다.

19.2 동적 컨텍스트 스위칭을 위한 반응형(Reactive) 제어 노드

기본적인 SequenceFallback 노드는 하위 노드가 RUNNING 상태일 때, 이전 노드들의 상태를 다시 평가하지 않고 즉각적으로 실행 권한을 해당 RUNNING 노드에 넘긴다. 동적 환경에서는 이러한 특성이 위험을 초래할 수 있으므로 BehaviorTree.CPP는 반응형(Reactive) 노드를 제공한다.

반응형 노드 타입학술적 동작 원리 및 용도
ReactiveFallback (또는 ReactiveSelector)하위 노드가 RUNNING 상태이더라도, 매 틱(Tick)마다 우선순위가 높은(왼쪽에 위치한) 자식 노드부터 다시 평가한다. 왼쪽 노드가 SUCCESS를 반환하면, 실행 중이던 오른쪽 하위 노드를 강제로 중단(Halt)시키고 컨텍스트를 전환한다. (인터럽트 패턴)
ReactiveSequence하위 노드가 RUNNING 상태일 때, 매 틱마다 이전(왼쪽에 위치한) 조건 노드들을 다시 평가한다. 조건이 FAILURE로 변경되면 즉시 실행 중인 노드를 중단(Halt)하고 상위로 FAILURE를 반환한다. (안전 조건 유지 패턴)

19.3 실시간 우선순위 조정 트리 아키텍처 패턴 (Subsumption Architecture)

포섭 구조(Subsumption Architecture)를 행동 트리로 구현하여 로봇의 생존과 직결된 행동이 일반적인 임무 행동을 압도(Subsume)하도록 설계한다.

계층적 설계 전략

루트 노드 바로 아래에 ReactiveFallback을 배치하고, 왼쪽부터 오른쪽으로 생존, 반응, 임무 순으로 서브 트리(SubTree)를 구성한다.

  1. 최우선순위 (왼쪽 1번 - 생존 로직): 배터리 방전 위험, 하드웨어 에러, 충돌 임박 등 로봇의 안전을 다루는 노드들을 배치한다.
  • 예: Condition: IsCollisionImminent -> Action: EmergencyStop
  1. 차순위 (왼쪽 2번 - 동적 반응 로직): 센서 퓨전 결과에 따라 나타나는 동적 장애물 회피나 사람 추종 등의 작업을 배치한다.
  • 예: Condition: IsHumanDetected -> Action: FollowHuman
  1. 최하위 (오른쪽 끝 - 기본 임무 로직): 우선순위가 높은 조건들이 모두 발동하지 않았을 때 수행되는 로봇의 기본 임무(예: 목표 지점 주행, 순찰)를 배치한다.
  • 예: Action: NavigateToWaypoint

컨텍스트 스위칭 발생 메커니즘:

로봇이 MapsToWaypoint를 실행(RUNNING) 중일 때 센서 퓨전 데이터가 사람을 감지하여 블랙보드의 is_human_detected 값을 true로 갱신하면, 다음 틱에서 ReactiveFallback은 왼쪽의 조건 노드를 재평가한다. 이때 차순위 트리가 활성화되면서 실행 중이던 이동 노드에 halt() 신호를 전송하여 모터를 정지시킨 후, FollowHuman 노드로 제어 권한을 전환한다.

19.4 XML 기반 컨텍스트 스위칭 트리 구현 명세

위에서 설명한 구조를 BehaviorTree.CPP의 XML 포맷으로 정의한 아키텍처 명세이다.

<root main_tree_to_execute="MainRobotLogic">
    <BehaviorTree ID="MainRobotLogic">
        <ReactiveFallback name="PriorityController">
            
            <ReactiveSequence name="EmergencyHandling">
                <Condition ID="CheckCollisionRisk" risk_level="{fused_risk_level}" />
                <Action ID="EmergencyStop" />
            </ReactiveSequence>

            <ReactiveSequence name="HumanInteraction">
                <Condition ID="IsHumanDetected" human_pose="{tracked_human_pose}" />
                <Action ID="TrackAndFollowHuman" target="{tracked_human_pose}" />
            </ReactiveSequence>

            <Sequence name="NominalNavigation">
                <Action ID="ComputePath" goal="{current_waypoint}" path="{planned_path}"/>
                <Action ID="FollowPath" path="{planned_path}" />
            </Sequence>

        </ReactiveFallback>
    </BehaviorTree>
</root>

이 설계 패턴을 적용하면 센서 데이터의 갱신 주기와 틱 주파수를 동기화하여, 환경의 불확실성에 강건하고 결정론적인(Deterministic) 실시간 의사결정 시스템을 구축할 수 있다.

20. ROS2 수명 주기(Lifecycle) 노드와의 아키텍처 통합 (Integration with Managed Nodes)

  1. ROS2 수명 주기(Lifecycle) 노드와의 아키텍처 통합 (Integration with Managed Nodes)

ROS2 생태계, 특히 Navigation2(Nav2) 스택은 시스템의 결정론적(Deterministic) 시작, 중지, 복구를 보장하기 위해 표준 rclcpp::Node 대신 rclcpp_lifecycle::LifecycleNode(관리형 노드, Managed Node) 기반의 아키텍처를 채택하고 있다. 행동 트리(Behavior Tree)가 로봇의 임무를 안정적으로 관장하기 위해서는 이러한 하위 제어기 및 센서 노드들의 수명 주기 상태(Lifecycle State)를 모니터링하고 논리적으로 동기화(Synchronization)하는 메커니즘이 필수적이다.

20.1 학술적 배경 및 수명 주기 상태 기계 (Lifecycle State Machine)

ROS2의 수명 주기 노드는 시스템의 자원 할당과 실행 상태를 엄격히 분리하기 위해 내부적으로 유한 상태 기계(Finite State Machine, FSM)를 운용한다. 행동 트리의 틱(Tick) 논리는 이 FSM의 현재 상태에 종속적으로 동작해야 한다.

  • 주요 기본 상태 (Primary States):
  • Unconfigured: 노드가 메모리에 로드되었으나, 파라미터나 통신 인프라(Publisher/Subscriber)가 초기화되지 않은 기저 상태.
  • Inactive: 설정이 완료되어 통신 객체는 생성되었으나, 실제 데이터 처리 및 전송(Execution)은 수행되지 않는 대기 상태.
  • Active: 노드가 정상적으로 데이터를 수신, 처리, 발행하며 주어진 임무를 수행하는 실행 상태.
  • Finalized: 노드가 소멸되기 직전의 상태로, 모든 자원이 반환된 상태.
  • 아키텍처적 제약: 행동 트리의 액션 노드(Action Node)가 모터 제어나 경로 계획을 요청하려면, 타겟이 되는 하위 서버 노드는 반드시 Active 상태여야 한다. Inactive 상태의 노드에 액션 목표(Goal)를 전송하면 요청이 거부(Reject)되거나 무시되어 시스템 실패(System Failure)로 이어진다.

20.2 행동 트리와 수명 주기 관리자의 상호작용 설계

행동 트리 내부에서 외부 노드의 수명 주기를 제어하거나 모니터링하기 위해서는 ROS2 Service 인터페이스(~/get_state, ~/change_state)를 활용해야 한다. 이를 객체지향적으로 캡슐화하기 위해 Nav2에서 제공하는 nav2_util::LifecycleServiceClient 래퍼(Wrapper) 클래스를 BT 노드 내부에 주입(Injection)하여 설계한다.

  • 상태 감시 (State Monitoring): BT 조건 노드(Condition Node)가 주기적으로 특정 노드의 상태를 서비스 호출로 질의(Query)하여 Active 여부에 따라 SUCCESS 또는 FAILURE를 반환한다.
  • 상태 전이 트리거 (State Transition Trigger): BT 액션 노드가 특정 이벤트(예: 하드웨어 복구) 발생 시, 외부 노드에 상태 전이(예: Inactive -> Active)를 요청하고, 전이가 완료될 때까지 RUNNING 상태를 반환하며 비동기적으로 대기한다.

20.3 수명 주기 동기화 BT 노드 C++ 구현 명세

특정 관리형 노드가 Active 상태에 도달할 때까지 대기하며 제어 흐름을 블로킹하지 않는 비동기 행동 트리 노드(WaitLifecycleStateNode)의 C++ 구현 명세이다.

#include <behaviortree_cpp_v3/action_node.h>
#include <rclcpp/rclcpp.hpp>
#include <nav2_util/lifecycle_service_client.hpp>
#include <lifecycle_msgs/msg/state.hpp>

class WaitLifecycleStateNode : public BT::StatefulActionNode
{
public:
    WaitLifecycleStateNode(const std::string& name, const BT::NodeConfiguration& config,
                           rclcpp::Node::SharedPtr ros_node)
        : BT::StatefulActionNode(name, config), ros_node_(ros_node)
    {
    }

    static BT::PortsList providedPorts()
    {
        return {
            BT::InputPort<std::string>("node_name", "Target lifecycle node name"),
            BT::InputPort<uint8_t>("target_state", "Target state ID (e.g., 3 for ACTIVE)")
        };
    }

    BT::NodeStatus onStart() override
    {
        std::string target_node_name;
        if (!getInput("node_name", target_node_name)) {
            RCLCPP_ERROR(ros_node_->get_logger(), "Missing [node_name] input");
            return BT::NodeStatus::FAILURE;
        }

        getInput("target_state", target_state_id_);

        // Lifecycle Service Client 동적 할당
        lifecycle_client_ = std::make_shared<nav2_util::LifecycleServiceClient>(
            target_node_name, ros_node_);

        return BT::NodeStatus::RUNNING;
    }

    BT::NodeStatus onRunning() override
    {
        // 서버 연결 확인
        if (!lifecycle_client_->checkService()) {
            RCLCPP_WARN_THROTTLE(ros_node_->get_logger(), *ros_node_->get_clock(), 1000,
                                 "Waiting for lifecycle service...");
            return BT::NodeStatus::RUNNING;
        }

        // 현재 상태 질의 (동기 호출 차단을 막기 위해 타임아웃 설정 고려)
        uint8_t current_state = lifecycle_client_->get_state();

        if (current_state == target_state_id_) {
            RCLCPP_INFO(ros_node_->get_logger(), "Target node reached the target state.");
            return BT::NodeStatus::SUCCESS;
        }

        // 상태가 일치하지 않으면 다음 틱까지 대기
        return BT::NodeStatus::RUNNING;
    }

    void onHalted() override
    {
        // 중단 시 클라이언트 포인터 초기화 자원 정리
        lifecycle_client_.reset();
    }

private:
    rclcpp::Node::SharedPtr ros_node_;
    std::shared_ptr<nav2_util::LifecycleServiceClient> lifecycle_client_;
    uint8_t target_state_id_;
};

20.4 결함 허용성 및 자동 복구 서브트리 (Auto-Recovery SubTree) 아키텍처

로봇 운용 중 하드웨어 장애나 네트워크 지연으로 인해 센서 드라이버나 제어 노드의 수명 주기가 Active에서 Inactive 또는 Unconfigured로 강제 강등(Downgrade)되는 예외 상황이 발생할 수 있다.

이러한 시스템 장애를 처리하기 위해 행동 트리의 ReactiveFallback 노드와 수명 주기 제어 노드를 결합하여 무중단 자동 복구 논리(Auto-Recovery Logic)를 XML로 명세한다.

<root main_tree_to_execute="MainMissionTree">
    <BehaviorTree ID="MainMissionTree">
        <ReactiveFallback name="Lifecycle_Guard_Controller">
            
            <ReactiveSequence name="Check_And_Recover_Lifecycle">
                <Inverter>
                    <Condition ID="IsLifecycleActive" node_name="controller_server"/>
                </Inverter>
                
                <Sequence name="Recover_Controller_Server">
                    <Action ID="TriggerLifecycleTransition" node_name="controller_server" transition="configure"/>
                    <Action ID="WaitLifecycleState" node_name="controller_server" target_state="2"/> <Action ID="TriggerLifecycleTransition" node_name="controller_server" transition="activate"/>
                    <Action ID="WaitLifecycleState" node_name="controller_server" target_state="3"/> </Sequence>
            </ReactiveSequence>

            <Sequence name="Nominal_Navigation">
                <Action ID="ComputePathToPose" goal="{goal}" path="{path}"/>
                <Action ID="FollowPath" path="{path}"/>
            </Sequence>

        </ReactiveFallback>
    </BehaviorTree>
</root>

설계 검증:

위 아키텍처에서 FollowPath를 수행하던 도중 controller_server에 크래시가 발생하여 상태가 변경되면, 최상단 ReactiveFallback이 이를 즉각 감지한다. 이로 인해 주행 액션(FollowPath)은 내부적으로 onHalted()가 호출되어 안전하게 정지되며, 트리의 흐름은 Recover_Controller_Server 서브트리로 전환되어 노드의 초기화(configure) 및 활성화(activate) 절차를 순차적이고 결정론적으로 재수행한다. 복구가 완료되면 다음 틱(Tick)에서 다시 주행 임무로 제어권이 원복된다.

21. 실시간(Real-Time) 제어 환경에서의 메모리 최적화 및 동시성(Concurrency) 관리

로봇 자율 제어 시스템은 환경의 변화와 제어 명령 간의 지연(Latency)을 최소화하기 위해 엄격한 실시간성(Real-Time Constraints)을 요구한다. 행동 트리(Behavior Tree)의 틱(Tick) 실행 엔진은 설정된 주파수(예: 100Hz)에 따라 결정론적(Deterministic)으로 동작해야 하며, 연산 지연이나 교착 상태(Deadlock)가 발생할 경우 로봇의 물리적 충돌이나 제어 불능 상태를 초래할 수 있다.

따라서 ROS2 생태계에서 행동 트리를 운용할 때는 메모리 단편화 및 할당 지연을 방지하는 메모리 최적화 기법과 멀티스레드 환경에서의 동시성 관리 아키텍처가 필수적으로 수반되어야 한다.

21.1 런타임 동적 메모리 할당(Dynamic Memory Allocation) 배제

운영체제(OS) 수준에서 힙(Heap) 영역에 메모리를 동적으로 할당(new, malloc)하거나 해제하는 과정은 컨텍스트 스위칭(Context Switching) 및 시스템 콜(System Call)을 유발하여 실행 시간에 예측 불가능한 지터(Jitter, 지연 시간의 변동성)를 발생시킨다.

  • 초기화 단계의 정적 할당(Static Allocation): 행동 트리를 구성하는 모든 제어 노드, 데코레이터 노드, 액션 노드의 인스턴스는 시스템 런타임(Runtime) 루프가 시작되기 전, 즉 노드의 초기화(Initialization) 또는 Unconfigured 단계에서 팩토리(BehaviorTreeFactory)를 통해 일괄 생성되어야 한다.
  • 컨테이너 크기 예약(Reservation): std::vectorstd::string과 같이 내부적으로 동적 할당을 수행하는 C++ 표준 라이브러리 컨테이너를 노드 연산(tick() 내부)에서 사용할 경우, reserve() 메서드를 통해 사전에 충분한 메모리 용량을 확보(Pre-allocation)하여 런타임 재할당(Reallocation)을 원천 차단해야 한다.

21.2 블랙보드(Blackboard) 경쟁 상태(Race Condition) 방지 및 스레드 안전성(Thread Safety)

ROS2 환경에서 행동 트리는 주로 메인 스레드(Tick Engine)에서 순차적으로 실행되는 반면, 센서 데이터를 수신하는 서브스크라이버(Subscriber) 콜백이나 하드웨어 인터럽트 처리는 백그라운드 스레드에서 비동기적으로 발생한다.

이러한 멀티스레드 환경에서 두 스레드가 동시에 블랙보드의 동일한 데이터 포트(Port)에 접근(Read/Write)할 경우 경쟁 상태(Race Condition) 및 데이터 오염(Data Corruption)이 발생한다.

  • 원자적 연산(Atomic Operations): 단순한 플래그(Boolean)나 정수형 센서 데이터의 갱신은 std::atomic<T>를 사용하여 락(Lock) 없이 원자적으로 처리함으로써 오버헤드를 최소화한다.
  • 상호 배제(Mutual Exclusion) 및 락(Lock) 최적화: 복잡한 구조체(예: geometry_msgs::msg::PoseStamped)를 블랙보드에 기록할 때는 std::mutexstd::scoped_lock을 사용하여 임계 구역(Critical Section)을 보호해야 한다. 단, 우선순위 역전(Priority Inversion)을 방지하기 위해 락을 유지하는 시간은 메모리 복사 연산 수준으로 극단적으로 짧아야 한다.

21.3 ROS2 멀티스레드 실행기(Multi-threaded Executor) 및 콜백 그룹(Callback Group) 아키텍처

행동 트리 틱 메커니즘이 ROS2 콜백 처리를 방해하지 않도록, 실행기(Executor) 계층에서의 스레드 분리 설계가 요구된다.

  • 실행 루프의 격리: 단일 스레드 실행기(SingleThreadedExecutor)를 사용할 경우, 긴 연산이 필요한 BT tick() 함수가 실행되는 동안 수신된 ROS2 토픽 메시지는 처리되지 못하고 큐(Queue)에 적체된다. 이를 방지하기 위해 rclcpp::executors::MultiThreadedExecutor를 도입하여 BT 실행 스레드와 ROS2 통신 스레드를 물리적으로 분리한다.
  • 상호 배타적 콜백 그룹(Mutually Exclusive Callback Group): 커스텀 BT 노드 내부에서 생성된 액션 클라이언트나 서브스크라이버는 각각 독립적인 콜백 그룹에 할당되어야 한다. 이를 통해 트리 연산이 진행 중이더라도 백그라운드에서 센서 피드백이나 액션 서버의 상태 갱신이 지연 없이 수신되도록 아키텍처를 구성한다.

21.4 C++ 구현 명세: 스레드 안전한(Thread-safe) 센서 데이터 버퍼링 노드

비동기 ROS2 콜백 스레드와 동기적 BT 틱 스레드 간의 데이터 교환을 안전하게 수행하는 커스텀 조건 노드(Condition Node)의 학술적 구현 명세이다.

#include <behaviortree_cpp_v3/condition_node.h>
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
#include <mutex>

class ObstacleDetectionNode : public BT::ConditionNode
{
public:
    ObstacleDetectionNode(const std::string& name, const BT::NodeConfiguration& config,
                          rclcpp::Node::SharedPtr ros_node)
        : BT::ConditionNode(name, config), ros_node_(ros_node), is_obstacle_detected_(false)
    {
        // ROS2 서브스크라이버를 별도의 상호 배타적 콜백 그룹에 할당
        callback_group_ = ros_node_->create_callback_group(
            rclcpp::CallbackGroupType::MutuallyExclusive);
            
        auto sub_opt = rclcpp::SubscriptionOptions();
        sub_opt.callback_group = callback_group_;

        scan_sub_ = ros_node_->create_subscription<sensor_msgs::msg::LaserScan>(
            "/scan", rclcpp::SensorDataQoS(),
            std::bind(&ObstacleDetectionNode::scanCallback, this, std::placeholders::_1),
            sub_opt);
    }

    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<double>("threshold_distance", "Distance to trigger obstacle flag") };
    }

    // BT 메인 스레드에서 호출되는 틱 함수
    BT::NodeStatus tick() override
    {
        bool current_status;
        
        // 데이터 경합을 방지하기 위한 최소한의 락 범위 설정
        {
            std::scoped_lock<std::mutex> lock(data_mutex_);
            current_status = is_obstacle_detected_;
        }

        if (current_status) {
            RCLCPP_WARN(ros_node_->get_logger(), "Obstacle detected! Returning FAILURE.");
            return BT::NodeStatus::FAILURE;
        }

        return BT::NodeStatus::SUCCESS;
    }

private:
    // ROS2 백그라운드 콜백 스레드에서 호출되는 함수
    void scanCallback(const sensor_msgs::msg::LaserScan::SharedPtr msg)
    {
        double threshold = 0.5; // 기본값
        getInput("threshold_distance", threshold); // 블랙보드로부터 설정값 읽기

        bool detected = false;
        for (float range : msg->ranges) {
            if (range < threshold && range > msg->range_min) {
                detected = true;
                break;
            }
        }

        // 스레드 안전성을 확보하며 내부 상태 갱신
        {
            std::scoped_lock<std::mutex> lock(data_mutex_);
            is_obstacle_detected_ = detected;
        }
    }

    rclcpp::Node::SharedPtr ros_node_;
    rclcpp::CallbackGroup::SharedPtr callback_group_;
    rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr scan_sub_;
    
    // 스레드 동기화를 위한 뮤텍스 및 버퍼 변수
    std::mutex data_mutex_;
    bool is_obstacle_detected_;
};

설계 검증:

위 C++ 명세는 tick() 호출 시 무거운 센서 데이터 처리 로직을 실행하지 않는다. 연산 부하가 높은 LiDAR 데이터 순회(scanCallback) 처리는 ROS2 멀티스레드 실행기에 의해 백그라운드에서 비동기적으로 수행된다. 메인 BT 스레드는 단지 std::mutex로 보호된 불리언(Boolean) 변수(is_obstacle_detected_)만을 복사하여 즉각적으로 상태를 평가하므로, 행동 트리의 틱 지연(Tick Latency)을 나노초(ns) 단위로 통제할 수 있다. 이는 실시간 운영체제(RTOS) 및 임베디드 로봇 제어 환경에서 요구되는 필수적인 소프트웨어 설계 패턴이다.

22. 블랙보드 스코프(Scope) 격리 및 계층적 메모리 구조 (Hierarchical Blackboard)

행동 트리(Behavior Tree)가 대규모 시스템으로 확장됨에 따라, 수백 개의 노드가 단일 전역 블랙보드(Global Blackboard)를 공유하는 방식은 데이터 무결성을 저해하고 ‘키 충돌(Key Collision)’ 문제를 야기할 수 있다. 소프트웨어 공학의 캡슐화(Encapsulation) 원칙을 준수하기 위해 BehaviorTree.CPP는 블랙보드의 계층적 격리 및 포트 리매핑(Port Remapping) 메커니즘을 제공한다.

22.1 블랙보드 계층 구조의 학술적 정의

행동 트리는 트리 구조를 따라 상속(Inheritance)과 격리(Isolation)가 결합된 메모리 모델을 채택한다.

  • 루트 블랙보드(Root Blackboard): 트리의 최상위 계층에서 생성되며, 모든 노드가 접근 가능한 전역 데이터를 저장한다.
  • 서브트리 블랙보드(SubTree Blackboard): 특정 <SubTree> 엘리먼트가 인스턴스화될 때 생성되는 독립적인 메모리 영역이다. 부모 블랙보드로부터 격리되어 내부 변수의 외부 유출을 방지한다.
  • 부모-자식 관계: 자식 블랙보드는 부모 블랙보드의 데이터를 참조(Read)할 수 있으나, 명시적인 포트 연결 없이는 부모의 데이터를 수정하거나 자신의 로컬 변수를 부모에게 노출하지 않는다.

22.2 포트 리매핑(Port Remapping)을 통한 데이터 흐름 제어

서브트리가 독립적인 모듈로서 재사용되기 위해서는 내부에서 사용하는 키(Key) 이름과 외부 블랙보드의 키 이름을 동적으로 연결하는 리매핑 과정이 필수적이다.

  • 입력 포트 리매핑: 부모 트리의 특정 데이터를 서브트리의 내부 변수로 전달한다.
  • 예: <SubTree ID="MoveRobot" target="{global_goal}" /> (부모의 global_goal을 서브트리의 target으로 매핑)
  • 출력 포트 리매핑: 서브트리 내부의 연산 결과를 부모 트리의 블랙보드에 기록한다.
  • 예: <SubTree ID="ComputePath" result="{planned_path}" /> (서브트리 내부의 result를 부모의 planned_path로 내보냄)

22.3 XML 기반 계층적 스코프 명세

다음은 전역 경로 계획과 국소 제어 서브트리가 각각 독립된 블랙보드 스코프를 유지하며 데이터를 교환하는 XML 구조의 예시다.

<root main_tree_to_execute="MainTree">
    <BehaviorTree ID="MainTree">
        <Sequence>
            <SubTree ID="PlanningSubTree" goal="{goal_pose}" out_path="{global_path}"/>
            
            <Action ID="FollowPath" path="{global_path}"/>
        </Sequence>
    </BehaviorTree>

    <BehaviorTree ID="PlanningSubTree">
        <Sequence>
            <Action ID="ComputePath" input_goal="{goal}" output_path="{result}"/>
            
            </Sequence>
    </BehaviorTree>
</root>

22.4 C++ 구현에서의 타입 안정성 및 메모리 격리

BehaviorTree.CPP의 Blackboard::create() 메서드는 기본적으로 부모 블랙보드에 대한 약한 참조(Weak Reference)를 가질 수 있도록 설계되어 있다.

  • 스코프 격리 구현: 서브트리 인스턴스화 시 Blackboard::create(parent_blackboard)를 호출하여 부모-자식 관계를 형성한다.
  • 타입 안정성(Type Safety): std::any 또는 자체적인 자료형 컨테이너를 통해 포트 간 데이터 전송 시 런타임 타입 검사(Run-time Type Checking)를 수행한다. 만약 리매핑된 두 키의 자료형이 불일치할 경우(예: PoseStampedint로 전달), 트리는 초기화 단계에서 예외를 발생시켜 시스템 크래시를 방지한다.
  • 데이터 은닉(Data Hiding): 서브트리 내부에서 리매핑되지 않은 키-값 쌍은 부모 트리에서 절대 참조할 수 없으므로, 대규모 협업 개발 환경에서 부수 효과(Side Effect)를 원천적으로 차단한다.

22.5 아키텍처적 이점

계층적 블랙보드 구조는 로봇 소프트웨어 공학 측면에서 다음과 같은 이점을 제공한다.

  1. 모듈의 독립성: 특정 기능을 수행하는 서브트리를 다른 프로젝트의 행동 트리에 복사하여 즉각 재사용할 수 있다.
  2. 디버깅 효율성: 블랙보드 스코프가 격리되어 있으므로 데이터 오염의 발원지를 특정 서브트리 내부로 한정하여 추적할 수 있다.
  3. 병렬성 확보: 서로 다른 서브트리가 동일한 키 이름을 내부적으로 사용하더라도 스코프가 분리되어 있어 간섭 없이 병렬 실행(Parallel execution)이 가능하다.

23. 기계학습(Machine Learning) 및 강화학습(RL) 정책과의 융합 아키텍처 (Hybrid Architecture with ML/RL Policies)

최근 자율 로봇 공학의 핵심 연구 과제는 결정론적(Deterministic)이고 수학적으로 검증 가능한 고전적 제어 아키텍처(행동 트리)와 불확실한 동적 환경에서 높은 적응성을 보이는 확률론적(Stochastic) 인공지능(심층 신경망, 강화학습) 모델을 결합하는 하이브리드 제어(Hybrid Control) 아키텍처의 설계이다. 행동 트리는 이러한 융합 시스템에서 인공지능의 행동을 통제하는 ‘안전 감독관(Safety Supervisor)’ 역할을 수행한다.

23.1 하이브리드 계층 제어 (Hybrid Hierarchical Control) 패러다임

  • 논리적 분리: 트리의 상위 계층(상위 제어기)은 이산적인 논리 조건(배터리 상태, 센서 고장, 충돌 위험 여부)을 평가하여 전역적인 상태 기계 흐름을 제어한다. 반면, 트리의 하위 제어기인 액션 노드(Action Node)에는 강화학습(RL) 기반의 연속 시간(Continuous Time) 보행 제어나 국소 경로 계획(Local Planning) 정책(Policy)이 위치한다.
  • 가관측성(Observability) 통합: ROS2의 퍼셉션 노드에서 융합된 상태 벡터(State Vector, 예: 라이다 포인트 클라우드, 관성 측정 데이터)는 행동 트리의 블랙보드를 통해 RL 액션 노드의 관측(Observation) 입력 포트로 전달된다.

23.2 기계학습 추론 엔진의 캡슐화 (Encapsulation of Inference Engines)

인공지능 모델을 행동 트리 아키텍처 내부에 통합하기 위해서는 GPU 가속 기반의 추론 로직을 BT::StatefulActionNode 내부에 캡슐화하거나, 독립된 ROS2 추론 서버와의 통신 인터페이스를 구축해야 한다.

  • 내장형 추론 노드 (Embedded Inference Node): BT::StatefulActionNodeonRunning() 콜백 내부에서 ONNX Runtime 또는 TensorRT C++ API를 직접 호출하여 텐서(Tensor) 연산을 수행한다. 이는 ROS2 네트워크 통신 지연(Latency)을 제거하므로 500Hz 이상의 고주파수 제어가 필요한 매니퓰레이터나 사족보행 로봇의 하위 제어에 적합하다.
  • 분산형 추론 인터페이스 (Distributed Inference Interface): 추론 연산 부하를 분산하기 위해, BtActionNode<ActionT>를 상속받아 고성능 GPU 연산 장치가 탑재된 별도의 ROS2 액션 서버로 추론 요청을 비동기 전송한다. 컴퓨팅 자원이 분리된 시스템 아키텍처에서 널리 채택된다.

23.3 안전 장치(Safety Guard) 및 심플렉스 아키텍처(Simplex Architecture) 연동

강화학습이나 신경망 모델은 학습되지 않은 분포 밖(Out-of-Distribution, OOD) 데이터가 입력될 경우 예측 불가능하고 위험한 제어 출력을 생성할 수 있다. 행동 트리의 Fallback (또는 Selector) 노드는 이러한 AI 모델의 실패를 수학적으로 보완하는 심플렉스 아키텍처를 구조적으로 구현하도록 지원한다.

  • 신뢰도 평가 (Confidence Evaluation): 기계학습 노드는 연산된 추론 결과(Action Command)와 함께 모델의 신뢰도 점수(Confidence Score)를 블랙보드에 출력 포트(Output Port)로 기록한다.
  • 논리적 폴백 (Logical Fallback): 조건 노드(Condition Node)가 신뢰도 점수를 평가하여 사전에 정의된 임계값(Threshold) 미만이거나, 제어 장벽 함수(Control Barrier Functions, CBF)를 위반할 궤적이 산출되었음이 감지되면 즉각 FAILURE를 반환한다.
  • 고전 제어 전환 (Switch to Classic Control): Fallback 노드의 라우팅 특성에 따라 실행 권한은 즉시 다음 자식 노드로 전이되며, DWA(Dynamic Window Approach)나 TEB(Timed Elastic Band)와 같이 리야프노프 안정성(Lyapunov Stability)이 수학적으로 증명된 고전 알고리즘 기반의 예비 액션 노드(Backup Action Node)가 실행된다.

23.4 XML 기반 하이브리드 제어 및 폴백 아키텍처 명세

다음은 강화학습 기반의 주행 정책(RL Navigation)이 낮은 예측 신뢰도를 보고할 경우, 고전적 경로 계획 알고리즘으로 즉각 제어권을 넘기는 계층적 안전 장치 구조의 XML 명세이다.

<root main_tree_to_execute="HybridNavigationTree">
    <BehaviorTree ID="HybridNavigationTree">
        <Sequence name="Mission_Sequence">
            
            <Action ID="UpdateStateVector" output_state="{rl_observation}"/>
            
            <Fallback name="Safety_Guarded_Navigation">
                
                <Sequence name="RL_Policy_Execution">
                    
                    <Action ID="RunInferenceTensorRT" 
                            input_tensor="{rl_observation}" 
                            action_cmd="{cmd_vel}" 
                            confidence="{ai_confidence}"/>
                    
                    <Condition ID="CheckConfidenceLevel" 
                               current_confidence="{ai_confidence}" 
                               min_threshold="0.85"/>
                               
                    <Action ID="PublishVelocity" velocity="{cmd_vel}"/>
                </Sequence>
                
                <Sequence name="Classic_Backup_Navigation">
                    <Action ID="ComputePathToPose" goal="{goal_pose}" path="{global_path}"/>
                    
                    <Action ID="FollowPathClassic" path="{global_path}"/>
                </Sequence>
                
            </Fallback>
            
        </Sequence>
    </BehaviorTree>
</root>

설계 검증:

이 아키텍처는 비정형 환경에서 기계학습 모델이 가진 인식의 불확실성(Uncertainty)을 행동 트리의 분기 로직(Branching Logic)을 통해 시스템 수준에서 흡수한다. AI가 분포 밖(OOD) 상황에 처해 ai_confidence가 임계치 미만으로 산출되면, 제어 흐름은 결정론적으로(Deterministically) 고전 제어기(FollowPathClassic)로 전환된다. 이는 행동 트리가 최신 제어 공학에서 딥러닝 모델의 ’블랙박스(Blackbox)’적 한계를 통제하고, 자율 주행 시스템 전체의 강건성(Robustness)과 안정성(Stability)을 보장하는 핵심 통합 프레임워크로 기능함을 학술적으로 증명한다.

24. 기호적 작업 계획(Symbolic Task Planning)과 행동 트리의 동적 생성 (Integration with PDDL & PlanSys2)

전통적인 행동 트리(Behavior Tree) 아키텍처는 설계자에 의해 사전에 정의된 정적(Static) XML 구조를 런타임에 파싱하여 결정론적으로 제어 흐름을 분기한다. 그러나 다목적 자율 로봇이 예측 불가능한 복잡한 목표를 달성해야 하는 환경에서는, 사전에 모든 예외 상황과 작업 순서를 하드코딩(Hard-coding)하는 것이 불가능하다. 이를 극복하기 위해 최신 로봇 소프트웨어 공학에서는 인공지능 자동 계획(Automated Planning) 기술인 PDDL(Planning Domain Definition Language)과 ROS2 PlanSys2 아키텍처를 결합하여, 기호적 추론(Symbolic Reasoning)의 결과를 실행 가능한 행동 트리로 동적 변환(Dynamic Generation)하는 패러다임을 채택한다.

24.1 학술적 배경: 기호적 추론과 반응형 실행의 계층적 분리

AI 작업 계획(Task Planning)과 행동 트리의 융합은 제어 시스템을 두 개의 추상화 계층으로 엄격히 분리한다.

  • 숙고 계층 (Deliberative Layer - PDDL): 시스템의 현재 상태(Initial State)와 목표 상태(Goal State)를 이산적인 기호(Symbol) 논리로 정의한다. 계획기(Planner, 예: POPF, TFD)는 상태 공간(State Space)을 탐색하여 목표를 달성하기 위한 최적의 거시적 행동 시퀀스(Plan)를 수학적 그래프(Graph) 형태로 산출한다.
  • 반응 계층 (Reactive Layer - Behavior Tree): 숙고 계층에서 산출된 거시적 행동(Action) 단위들은 각각 하나의 서브트리(SubTree) 또는 BT 노드에 1:1로 매핑된다. 행동 트리는 센서 피드백을 통해 물리적 환경과 상호작용하며 기호적 행동을 연속 시간(Continuous Time) 제어 영역에서 실제 물리적 구동(Execution)으로 변환하고, 실패 시 로컬 복구(Local Recovery)를 수행한다.

24.2 PDDL 도메인(Domain) 모델링과 BT 액션의 매핑(Mapping)

PlanSys2 프레임워크에서 행동 트리의 동적 생성을 위해서는 먼저 PDDL을 통해 로봇의 물리적 제약조건을 논리식으로 모델링해야 한다. PDDL 도메인의 각 durative-action은 BehaviorTree.CPP의 BtActionNode 인스턴스와 직접적으로 결합된다.

  • PDDL 명세의 조건적 제약: 액션이 실행되기 위한 전제 조건(Precondition)과 액션 완료 후 변화하는 환경의 사후 조건(Effect)을 명시한다.
  • 매핑 메커니즘: PDDL의 인자(Arguments)는 실행 시 행동 트리의 블랙보드(Blackboard) 입력 포트(Input Port)로 파라미터화되어 주입된다.
;; PDDL 도메인: 로봇의 이동 액션 정의 (숙고 계층)
(:durative-action move
    :parameters (?r - robot ?wp1 ?wp2 - waypoint)
    :duration ( = ?duration 10)
    :condition (and
        (at start (robot_at ?r ?wp1))
        (over all (path_clear ?wp1 ?wp2))
    )
    :effect (and
        (at start (not (robot_at ?r ?wp1)))
        (at end (robot_at ?r ?wp2))
    )
)

위의 PDDL 액션 move는 PlanSys2의 액션 실행기(Action Executor) 내부에 등록된 특정 행동 트리 XML 서브트리(예: Maps_to_waypoint.xml)를 호출하는 식별자로 작용한다.

24.3 ROS2 PlanSys2 아키텍처 기반 행동 트리 런타임 동적 생성

목표(Goal)가 하달되어 PDDL 계획기(Planner)가 유효한 계획(Plan)을 도출하면, PlanSys2의 실행기(Executor) 모듈은 산출된 이산 액션들의 시간적 선후 관계를 바탕으로 메모리 상에서 메인 행동 트리(Master Behavior Tree)를 동적으로 인스턴스화한다.

  1. 계획 그래프(Plan Graph) 파싱: 도출된 계획의 병렬성(Concurrency)과 의존성(Dependency)을 분석한다.
  2. 동적 트리 조립 (Dynamic Tree Assembly):
  • 직렬로 실행되어야 하는 PDDL 액션들은 BT의 Sequence 제어 노드 하위에 결합된다.
  • 동시에 실행 가능한 액션(예: 이동과 카메라 패닝)은 BT의 Parallel 제어 노드 하위에 결합된다.
  1. 포트 동적 바인딩 (Dynamic Port Binding): PDDL 계획 상의 구체적 기호(예: wp_kitchen)는 문자열 역직렬화를 통해 BT 블랙보드의 키-값(Key-Value) 쌍으로 변환 적재된다.

24.4 XML 동적 생성 및 실행(Execution) 명세 패턴

PlanSys2는 실행 시 메모리 상에서 다음과 같은 구조의 메인 XML 트리를 런타임에 동적으로 조합하여 BehaviorTreeFactory로 전달한다. 이는 개발자가 하드코딩한 것이 아닌, 추론 엔진에 의해 그 시점의 상태에 맞추어 임시로 생성된(Transient) 제어 로직이다.

<root main_tree_to_execute="DynamicPlanTree">
    <BehaviorTree ID="DynamicPlanTree">
        <Sequence name="Symbolic_Plan_Execution">
            
            <Action ID="ExecutePlanSys2Action" 
                    action_name="move" 
                    args="robot1 wp_home wp_table" 
                    bt_xml_file="navigate_bt.xml" />
                    
            <Action ID="ExecutePlanSys2Action" 
                    action_name="pick_object" 
                    args="robot1 wp_table obj_cup" 
                    bt_xml_file="manipulation_bt.xml" />
                    
        </Sequence>
    </BehaviorTree>
</root>

24.5 실패 전파(Failure Propagation) 및 재계획(Re-planning) 메커니즘

행동 트리는 동적 환경에서의 단기적 반응성에 최적화되어 있으나, 복구 불가능한 교착 상태에 빠지면 기호적 재계획이 필수적이다.

  • 로컬 복구의 한계 도달: manipulation_bt.xml 내부의 로컬 Fallback 노드가 설정된 재시도(Retry) 횟수를 초과하여 최종적으로 FAILURE 상태를 상위 동적 트리로 반환한다.
  • 상태 공간의 무효화 (State Invalidation): PlanSys2의 실행기는 메인 BT로부터 FAILURE를 수신하면, 즉시 로봇의 실행을 중단(Halt)한다.
  • PDDL 재계획 (Re-planning): 센서 데이터를 바탕으로 세계 모델(World Model)의 기호적 상태(예: (not (path_clear wp_home wp_table)))를 갱신한 후, 변경된 초기 상태를 바탕으로 계획기를 다시 호출한다. 이후 완전히 새로운 구조의 행동 트리가 다시 동적 생성되어 실행 루프가 재개된다.

이 아키텍처는 행동 트리의 구조적 캡슐화 및 반응성(Reactivity)을 유지하면서도, 복잡한 다중 목표를 자율적으로 추론할 수 있는 엔터프라이즈급 로봇 인지-행동 시스템의 필수적 소프트웨어 설계 패턴이다.

25. 정형 검증(Formal Verification) 및 모델 체킹(Model Checking) 기반 안정성 수학적 증명

로봇 자율 제어 시스템이 항공우주, 자율주행 자동차, 의료 로봇 등 안전 필수(Safety-Critical) 환경에 배치될 경우, 경험적인 단위 테스트나 시뮬레이션만으로는 시스템의 절대적인 무결성을 보장할 수 없다. 행동 트리(Behavior Tree, BT) 아키텍처가 특정 위험 상태(Unsafe State)에 절대 도달하지 않으며, 제어 흐름의 교착 상태(Deadlock)나 라이브락(Livelock) 없이 임무를 수행함을 수학적으로 증명하기 위해 정형 검증(Formal Verification) 기법이 도입된다.

25.1 행동 트리의 수학적 상태 전이 모델링 (Mathematical Modeling)

행동 트리를 정형 검증 도구로 분석하기 위해서는 틱(Tick)의 전파와 반환(SUCCESS, FAILURE, RUNNING), 그리고 블랙보드(Blackboard) 변수의 갱신 과정을 유한 상태 전이 시스템(Finite State Transition System, FSTS)으로 수학적으로 추상화해야 한다.

행동 트리의 상태 전이 시스템 M은 다음과 같은 튜플(Tuple)로 정의된다.
M = (S, S_0, R, L)

  • S: 시스템의 모든 가능한 상태의 유한 집합이다. 이는 블랙보드 내 이산 변수들의 조합과 각 BT 노드의 활성화(Active/Inactive) 상태를 포함한다.
  • S_0 \subseteq S: 시스템의 초기 상태 집합이다.
  • R \subseteq S \times S: 상태 전이 관계(Transition Relation)이다. 루트 노드에서 발생한 틱(Tick) 신호에 의해 액션 노드가 연산을 수행하고 블랙보드 값을 변경하거나 상태를 반환하는 논리적 인과관계를 나타낸다.
  • L: S \rightarrow 2^{AP}: 원자 명제 집합(Atomic Propositions, AP)에 대한 레이블링 함수로, 특정 상태 S에서 참(True)이 되는 논리적 속성들을 매핑한다.

25.2 시제 논리(Temporal Logic) 기반 검증 명세 (Specification)

검증하고자 하는 로봇의 제어 요구사항은 시스템의 상태가 시간에 따라 어떻게 변화해야 하는지를 규정하는 시제 논리, 주로 선형 시제 논리(Linear Temporal Logic, LTL) 또는 연산 트리 논리(Computation Tree Logic, CTL)로 정형화된다.

  • 안전성 (Safety): “시스템에 재앙적인 일은 절대 발생하지 않는다.“를 증명한다. LTL의 전역 연산자 \mathbf{G} (Globally)를 사용하여 명세한다.

예: \mathbf{G} \neg (is\_moving \land obstacle\_detected) (전체 실행 경로에서 장애물이 감지되었는데 로봇이 이동하는 상태는 절대 존재하지 않는다.)

  • 활성 및 도달 가능성 (Liveness & Reachability): “시스템이 결국에는 원하는 목표를 달성한다.“를 증명한다. LTL의 미래 연산자 \mathbf{F} (Eventually)를 사용한다.

예: \mathbf{F} (goal\_reached \lor returned\_to\_base) (시스템은 무한 루프에 빠지지 않고 결국 목표 지점에 도달하거나, 실패 시 복귀 지점으로 돌아온다.)

25.3 모델 체킹 도구 (Model Checkers) 연동 아키텍처

XML과 C++로 작성된 행동 트리는 직접적인 수학적 연역이 불가능하므로, 정형 검증기(Model Checker)가 해석할 수 있는 언어로 컴파일(Compile)하는 파이프라인이 요구된다.

  • NuSMV 적용: 트리의 순수 논리적 제어 흐름(Sequence, Fallback 분기)과 이산적인 블랙보드 데이터의 상태 정합성을 검증하는 데 주로 사용된다. C++의 BT 노드 반환 로직을 SMV 언어의 CASE 구문으로 치환하여 모든 가능한 실행 경로(State Space)를 전수 탐색한다.
  • UPPAAL 적용: 시간 자동자(Timed Automata) 모델을 기반으로 하므로, 액션 노드의 실행 지연(Execution Time)이나 Timeout 데코레이터 등 엄격한 실시간성(Hard Real-time) 제약이 포함된 행동 트리의 스케줄링 가능성 및 타이밍 결함을 검증하는 데 학술적으로 타당하다.

25.4 상태 공간 폭발(State Space Explosion) 방지 및 추상화

블랙보드에 저장되는 로봇의 좌표(x, y, \theta), 배터리 잔량 등의 연속형(Continuous) 데이터를 그대로 모델 체커에 입력할 경우 검증해야 할 상태의 수가 무한대로 발산하는 상태 공간 폭발 문제가 발생한다. 이를 제어하기 위해 시스템 아키텍처에 추상화(Abstraction) 기법을 적용해야 한다.

  • 술어 추상화 (Predicate Abstraction): 연속적인 실수형 데이터를 행동 트리의 조건 노드가 평가하는 기준 임계값(Threshold)을 바탕으로 이산적인 참/거짓 논리로 사상(Mapping)한다.

예: 실수 변수 voltage \in [0.0, 12.0] 대신 논리 변수 voltage < 11.5 \rightarrow is\_battery\_low = true 로 상태를 단순화하여 검증 복잡도를 기하급수적으로 낮춘다.

  • 가정-보장 추론 (Assume-Guarantee Reasoning): 거대한 메인 트리를 한 번에 검증하지 않고 서브트리 단위로 분해한다. 특정 서브트리가 외부로부터 올바른 입력을 받는다는 가정(Assume) 하에, 자신의 목표 상태를 항상 보장(Guarantee)함을 개별적으로 증명한 뒤 이를 결합하여 전체 시스템의 무결성을 수학적으로 입증한다.

결론적으로 정형 검증 아키텍처는 행동 트리의 구조적 캡슐화를 수학적 명제로 치환함으로써, 코너 케이스(Corner Case)에서의 예기치 않은 동작이나 복구 제어 흐름(Fallback)의 논리적 결함을 실선 배치 이전에 원천적으로 차단하는 최상위 소프트웨어 신뢰성 보증 기법이다.

26. 다중 에이전트 시스템(Multi-Agent Systems)을 위한 군집 분산 행동 트리 (Distributed Behavior Trees)

단일 로봇의 자율성을 넘어, 복수의 로봇이 협력하여 광역 탐사, 물류 이송, 또는 군집 비행(Swarm Flight)을 수행하는 다중 에이전트 시스템(Multi-Agent Systems, MAS)에서는 개별 로봇의 제어기를 연결하는 분산형 의사결정 아키텍처가 요구된다. 분산 행동 트리(Distributed Behavior Trees, DBT)는 전통적인 행동 트리의 계층적 논리를 네트워크 상의 여러 컴퓨팅 노드로 확장하여, 군집 전체의 전역적 목표(Global Goal)를 개별 에이전트의 지역적 행동(Local Action)으로 매핑(Mapping)하는 구조적 방법론이다.

26.1 분산 제어 패러다임: 틱 위임(Tick Delegation)과 구조적 분리

분산 행동 트리는 단일 중앙 서버가 모든 로봇의 하드웨어를 직접 제어하는 중앙집중형(Centralized) 방식의 통신 병목 및 단일 장애점(Single Point of Failure, SPOF) 문제를 극복하기 위해 설계된다.

  • 틱의 네트워크 전파 (Networked Tick Propagation): 리더(Leader) 로봇이나 엣지 서버(Edge Server)에 위치한 ’전역 임무 트리(Global Mission Tree)’가 틱(Tick)을 발생시키면, 이 신호는 네트워크를 통해 팔로워(Follower) 로봇의 ’지역 실행 트리(Local Execution Tree)’로 위임(Delegation)된다.
  • 원격 액션 노드 (Remote Action Node): 전역 트리 내부에 배치된 특수한 액션 노드이다. 자체적인 물리적 연산을 수행하지 않고, ROS2 Action Client 통신을 통해 원격 에이전트(다른 로봇)의 행동 트리를 트리거(Trigger)하며, 네트워크 너머에서 반환된 SUCCESS, FAILURE, RUNNING 상태를 수신하여 상위 노드로 전달한다.

26.2 분산 블랙보드 동기화 (Distributed Blackboard Synchronization)

여러 로봇이 협력하기 위해서는 환경에 대한 인식과 각자의 현재 상태를 공유하는 전역 메모리 공간이 필요하다. ROS2 환경에서는 DDS(Data Distribution Service) 미들웨어를 활용하여 분산 블랙보드 아키텍처를 구현한다.

  • 계층적 상태 공유:
  • 지역 블랙보드 (Local Blackboard): 각 로봇의 배터리 잔량, 모터 온도, 국소 코스트맵 등 자신만이 접근하고 수정하는 격리된 메모리 공간이다.
  • 전역 블랙보드 (Global Blackboard): 군집 전체가 공유하는 데이터(예: 발견된 타겟 좌표, 할당된 임무 목록)를 저장한다.
  • 데이터 일관성 (Data Consistency): 전역 블랙보드의 항목이 변경될 때마다 ROS2 토픽(Topic)을 통해 네트워크 상의 모든 에이전트에게 브로드캐스트(Broadcast)된다. 네트워크 지연으로 인한 경쟁 상태(Race Condition)를 방지하기 위해, 특정 키(Key)에 대한 쓰기 권한(Write Access)은 상호 배제(Mutual Exclusion) 알고리즘이나 분산 락(Distributed Lock)을 통해 단일 에이전트에게만 일시적으로 부여된다.

26.3 ROS2 통신 기반 다중 에이전트 트리 XML 아키텍처 명세

군집 시스템에서 리더 에이전트가 팔로워 에이전트(로봇 A, 로봇 B)에게 병렬적으로 작업을 할당하고 모니터링하는 분산 행동 트리의 XML 구조 예시이다.

<root main_tree_to_execute="SwarmMissionTree">
    <BehaviorTree ID="SwarmMissionTree">
        <Sequence name="Collaborative_Search_Task">
            
            <Action ID="SyncGlobalBlackboard" topic_name="/swarm/global_state"/>
            
            <Parallel success_threshold="2" failure_threshold="1">
                
                <Action ID="RemoteActionNode" 
                        action_server_name="/agent_a/execute_bt" 
                        target_subtree="SearchZoneA" 
                        timeout="60.0"/>
                        
                <Action ID="RemoteActionNode" 
                        action_server_name="/agent_b/execute_bt" 
                        target_subtree="RetrieveObject" 
                        target_object="{target_coords}"
                        timeout="60.0"/>
                        
            </Parallel>
        </Sequence>
    </BehaviorTree>
</root>

위 명세에서 RemoteActionNode는 ROS2 Action 네트워크 계층을 통해 페이로드(Payload)로 target_subtree 식별자를 전송하며, 팔로워 로봇은 자신의 메모리에 적재된 해당 서브트리를 실행하게 된다.

26.4 통신 단절(Network Partition) 및 분산 복구(Distributed Fallback) 메커니즘

분산 환경의 핵심 한계인 패킷 손실(Packet Loss)이나 통신 음영 지역 진입 시, 시스템이 교착 상태에 빠지는 것을 방지하기 위한 강건성(Robustness) 설계가 필수적이다.

  • 타임아웃 및 심박수(Heartbeat) 감시: RemoteActionNode는 비동기 호출 후 대상 로봇으로부터 주기적인 피드백(Heartbeat)이 수신되지 않거나 timeout이 만료되면 즉각 FAILURE를 반환한다.
  • 리더 측의 작업 재할당 (Task Reallocation Fallback): FAILURE를 수신한 리더 측 트리에서는 Fallback 노드를 통해 통신이 두절된 로봇 A의 임무를 대기 중인 로봇 C에게 동적으로 재할당(Reassign)하는 복구 로직으로 전이한다.
  • 팔로워 측의 자율 생존 (Autonomous Survival Fallback): 네트워크가 단절된 팔로워 로봇은 틱을 수신하지 못하더라도 자체적인 ’생존 트리(Survival Tree)’를 독립적으로 가동하여, 안전 구역(Safe Zone)으로 이동하거나 통신 복구 위치로 귀환(Return-to-Comms)하는 로컬 폴백을 수학적으로 보장해야 한다.

27. Micro-ROS 기반 RTOS(임베디드) 환경에서의 경량화 행동 트리 아키텍처

로봇 자율 제어 시스템의 말단 연산 장치(Edge Device)인 마이크로컨트롤러(MCU)는 고성능 마이크로프로세서(SBC)와 달리 가용 메모리(RAM/ROM)가 수십~수백 킬로바이트(KB) 수준으로 극도로 제한되어 있다. 표준 ROS2 환경에서 구동되는 BehaviorTree.CPP 라이브러리는 동적 메모리 할당(Dynamic Memory Allocation)과 XML 파싱(Parsing)에 크게 의존하므로, FreeRTOS나 Zephyr와 같은 실시간 운영체제(RTOS) 환경에서는 메모리 단편화(Memory Fragmentation)와 지연(Latency)을 유발하여 적용이 불가능하다.

따라서 Micro-ROS 생태계 내에서 하드웨어 밀착형 제어를 수행하기 위해서는, 강엄격한 실시간성(Hard Real-time)을 보장하는 초경량(Lightweight) 행동 트리(Behavior Tree, BT) 아키텍처 설계가 학술적, 공학적으로 요구된다.

27.1 동적 메모리 할당(Heap Allocation)의 완전한 배제

RTOS 환경에서 힙(Heap) 메모리의 동적 할당(new, malloc) 및 해제(delete, free)는 실행 시간의 비결정성(Non-determinism)을 초래하며, 장기 가동 시 메모리 단편화로 인한 시스템 패닉(Out-of-Memory, OOM)을 유발한다.

  • 정적 인스턴스화 (Static Instantiation): 행동 트리를 구성하는 모든 노드(제어 노드, 액션 노드, 조건 노드)는 런타임 이전(컴파일 타임 또는 전역 변수 초기화 단계)에 BSS(Block Started by Symbol) 영역이나 정적 데이터 영역(Data Segment)에 메모리가 사전 할당(Pre-allocated)되어야 한다.
  • 표준 템플릿 라이브러리(STL) 사용 제한: 내부적으로 힙 영역을 사용하는 std::vector, std::string, std::shared_ptr의 사용을 엄격히 금지한다. 대신 고정 크기의 std::array, 스택(Stack) 기반 버퍼, 그리고 원시 포인터(Raw Pointer)나 참조자(Reference)로 대체하여 메모리 발자국(Memory Footprint)을 통제한다.

27.2 XML 파싱 부하 제거 및 C++ 하드코딩 빌드 패턴

표준 BT 아키텍처는 런타임 유연성을 위해 XML 문자열을 파싱하여 트리를 동적으로 구성한다. 그러나 임베디드 환경에서는 XML 파서(Parser)를 적재할 ROM 용량이 부족하며, 파싱 연산 자체가 불필요한 오버헤드이다.

  • 구조적 결합 (Structural Coupling): 트리의 계층 구조는 C++ 소스 코드 내에서 객체 간의 포인터 연결을 통해 하드코딩(Hard-coding) 형태로 명세된다.
  • 정적 빌더 패턴 (Static Builder Pattern): 부모 노드가 자식 노드의 배열 포인터와 개수를 초기화 인자로 직접 주입받아 제어 흐름 라우팅 구조를 런타임 이전에 확정한다.

27.3 정적 블랙보드(Static Blackboard) 기반 메모리 풀(Memory Pool) 모델

표준 블랙보드 아키텍처는 타입 은닉(std::any)과 해시 맵(std::unordered_map)을 사용하여 데이터를 동적으로 저장하므로 RTOS 환경에 부적합하다.

  • 정적 구조체 기반 블랙보드: 로봇이 공유해야 할 모든 상태 변수(예: 모터 엔코더 값, 센서 플래그)를 컴파일 타임에 결정되는 단일 전역 구조체(struct) 또는 정적 메모리 풀로 정의한다.
  • 직접 주소 참조 (Direct Address Referencing): BT 노드는 문자열 키(Key)를 통한 해시 검색 대신, 정적 구조체의 특정 멤버 변수에 대한 메모리 주소(Pointer)를 초기화 시점에 전달받아 O(1)의 시간 복잡도로 데이터에 직접 접근(Read/Write)한다.

27.4 Micro-ROS 에이전트 통신 및 RTOS 스케줄링 통합

Micro-ROS는 uXRCE-DDS 프로토콜을 사용하여 MCU와 ROS2 호스트 간의 브리지 통신을 수행한다. 행동 트리의 틱(Tick) 스레드와 통신 스레드는 RTOS의 스케줄러에 의해 관리되어야 한다.

  • 비동기 실행기 통합: Micro-ROS의 rclc_executor_t를 통해 센서 구독(Subscription) 및 타이머 콜백을 처리하고, 행동 트리의 tickRoot() 함수는 RTOS의 특정 주기적 태스크(Periodic Task) 내부에서 실행된다.
  • 비블로킹(Non-blocking) 통신: 통신 지연이 틱 실행 루프를 차단하지 않도록, 통신 태스크와 BT 태스크의 우선순위(Priority)를 엄격히 분리하고 인터럽트 서비스 루틴(ISR)과의 데이터 교환은 RTOS 큐(Queue)나 뮤텍스(Mutex)가 배제된 원자적(Atomic) 변수를 통해 수행한다.

27.5 경량화 BT 노드 C++ 구현 명세 (RTOS 타겟)

다음은 동적 할당 없이 배열과 원시 포인터를 활용하여 정적으로 조립되는 경량화 Sequence 노드와 하위 Condition 노드의 C++ 구조 명세이다.

#include <stdint.h>
#include <stdbool.h>

// 노드 상태 열거형
typedef enum {
    BT_SUCCESS,
    BT_FAILURE,
    BT_RUNNING
} NodeStatus;

// 경량화 기반 노드 인터페이스
class TreeNode {
public:
    virtual NodeStatus tick() = 0;
};

// 동적 할당을 배제한 정적 순차(Sequence) 노드
class StaticSequenceNode : public TreeNode {
private:
    TreeNode** children_;
    uint8_t child_count_;
    uint8_t current_child_idx_;

public:
    // 생성 시점에 자식 노드 포인터 배열과 크기를 주입받음
    StaticSequenceNode(TreeNode** children, uint8_t count) 
        : children_(children), child_count_(count), current_child_idx_(0) {}

    NodeStatus tick() override {
        while (current_child_idx_ < child_count_) {
            NodeStatus status = children_[current_child_idx_]->tick();

            if (status == BT_RUNNING) {
                return BT_RUNNING;
            } else if (status == BT_FAILURE) {
                current_child_idx_ = 0; // 실패 시 초기화
                return BT_FAILURE;
            }
            current_child_idx_++; // 성공 시 다음 노드로 전이
        }
        current_child_idx_ = 0; // 모두 성공 시 초기화
        return BT_SUCCESS;
    }
};

// 정적 블랙보드를 참조하는 센서 평가 노드
class BatteryConditionNode : public TreeNode {
private:
    const float* battery_voltage_ptr_; // 정적 블랙보드 변수에 대한 원시 포인터
    const float threshold_;

public:
    BatteryConditionNode(const float* v_ptr, float threshold)
        : battery_voltage_ptr_(v_ptr), threshold_(threshold) {}

    NodeStatus tick() override {
        // 문자열 검색 없이 메모리 직접 참조 평가
        if (*battery_voltage_ptr_ > threshold_) {
            return BT_SUCCESS;
        }
        return BT_FAILURE;
    }
};

// --- RTOS 메인 태스크 초기화 예시 ---
// 1. 전역 블랙보드 (BSS 영역)
float global_battery_voltage = 12.0f;

// 2. 리프 노드 정적 인스턴스화
BatteryConditionNode check_battery(&global_battery_voltage, 11.0f);
// (기타 액션 노드 선언 생략...)

// 3. 트리 토폴로지 정적 배열 매핑
TreeNode* sequence_children[] = { &check_battery /*, &move_action */ };

// 4. 루트 제어 노드 인스턴스화
StaticSequenceNode root_sequence(sequence_children, 1);

// RTOS 타이머 콜백 또는 주기적 태스크에서 호출
void rtos_bt_task() {
    root_sequence.tick();
}

설계 검증:

위 명세는 new 키워드나 가변 길이 컨테이너를 전혀 사용하지 않는다. 모든 메모리는 컴파일 타임에 결정되며, 틱(Tick) 신호의 라우팅은 캐시(Cache) 친화적인 배열 순회를 통해 이루어진다. 블랙보드 접근 시간 역시 포인터 역참조(Dereference) 수준으로 최적화되어 있으므로, Micro-ROS 에이전트와 통합되어 수 킬로바이트(KB)의 램(RAM)만으로도 강엄격한 실시간성 제어를 완벽히 보장할 수 있는 학술적, 공학적 타당성을 갖는다.

28. 모방 학습(Imitation Learning) 및 유전 알고리즘 기반 행동 트리 자동 합성 (Data-Driven BT Synthesis)

인간 엔지니어의 휴리스틱(Heuristic)에 의존하여 행동 트리(Behavior Tree, BT)의 XML 구조를 수동으로 설계하는 전통적인 방식은 로봇이 직면하는 고차원 상태 공간(High-dimensional State Space)과 예측 불가능한 엣지 케이스(Edge Case)를 모두 포괄하기 어렵다. 이러한 한계를 수학적이고 알고리즘적으로 극복하기 위해, 로봇 제어 학계에서는 시연 데이터(Demonstration Data)로부터 제어 로직을 추출하거나 진화 연산을 통해 최적의 트리 토폴로지(Topology)를 자동 합성(Automated Synthesis)하는 데이터 주도적(Data-Driven) 방법론을 채택하고 있다.

28.1 학술적 배경: 제어 논리의 자동화와 탐색 공간

행동 트리의 자동 합성은 주어진 목적 함수(Objective Function)를 최대화하거나 비용 함수(Cost Function)를 최소화하는 이산 최적화(Discrete Optimization) 문제로 정의된다. 행동 트리는 노드의 종류(제어, 조건, 액션)와 계층적 배치 순서에 따라 그 경우의 수가 기하급수적으로 증가하므로, 전수 탐색(Exhaustive Search)은 NP-Hard 문제에 해당한다.

따라서 이 탐색 공간(Search Space)을 효율적으로 수렴시키기 위해 모방 학습(Imitation Learning)을 통한 지도 학습(Supervised Learning) 방식과, 유전 프로그래밍(Genetic Programming, GP)을 통한 경험적 탐색(Heuristic Search) 방식이 결합되어 아키텍처 파이프라인을 구성한다.

28.2 모방 학습 기반 의사결정 나무(Decision Tree) 추출 및 BT 변환

전문가(인간 조작자)의 원격 조작(Teleoperation) 시연을 통해 로봇의 상태(State)와 그에 따른 행동(Action) 쌍의 데이터셋 \mathcal{D}를 수집한다.
\mathcal{D} = \{(s_i, a_i)\}_{i=1}^N
여기서 s_i \in \mathcal{S}는 시간 i에서의 연속형 또는 이산형 센서 관측 벡터(블랙보드 상태)이며, a_i \in \mathcal{A}는 해당 상태에서 전문가가 선택한 이산적 액션(또는 하위 제어기 호출 명령)이다.

  1. 의사결정 나무(Decision Tree) 학습: CART(Classification and Regression Trees) 알고리즘 등을 사용하여 지니 불순도(Gini Impurity)나 정보 획득량(Information Gain)을 최적화하는 의사결정 나무 모델 T를 학습한다.
  2. 행동 트리(BT)로의 동형 사상 (Isomorphic Mapping): 학습된 의사결정 나무는 구조적으로 행동 트리와 수학적 동치 변환이 가능하다.
  • 의사결정 나무의 분기 노드(Split Node, 예: x < \theta)는 행동 트리의 Condition 노드로 매핑된다.
  • 의사결정 나무의 제어 흐름은 행동 트리의 Fallback 노드와 Sequence 노드의 조합으로 치환된다.
  • 의사결정 나무의 리프 노드(Leaf Node, 클래스 레이블)는 행동 트리의 Action 노드로 매핑된다.

이 변환 과정을 통해 생성된 행동 트리는 데이터에 내재된 인간의 제어 정책을 모사(Imitate)하며, 블랙박스 형태의 심층 신경망(DNN)과 달리 C++ 및 XML 기반으로 구조화되므로 설명 가능성(Explainability)을 유지한다.

28.3 유전 프로그래밍(Genetic Programming) 기반 트리 구조 진화 연산

모방 학습만으로는 시연 데이터에 포함되지 않은 미지의 상태(Out-of-Distribution)에 대한 강건성을 보장할 수 없다. 이를 보완하기 위해 유전 알고리즘 기반의 구조적 진화(Evolutionary Search)를 적용하여 트리를 최적화한다.

  • 개체 표현 (Individual Representation): 진화 알고리즘의 한 세대(Generation)는 다수의 행동 트리(개체)로 구성되며, 각 트리는 추상 구문 트리(AST, Abstract Syntax Tree)로 메모리에 표현된다.

  • 적합도 함수 (Fitness Function): 물리 엔진 기반 시뮬레이터(예: Gazebo, Isaac Sim) 내에서 생성된 행동 트리를 실행하고 그 성능을 수학적으로 평가한다. 적합도 함수 J는 다음과 같이 정의된다.
    J = \omega_1 \cdot P_{success} - \omega_2 \cdot T_{exec} - \omega_3 \cdot N_{nodes}
    (단, P_{success}는 임무 성공률, T_{exec}는 실행 시간, N_{nodes}는 트리의 노드 수로 로직의 간결성을 유도하는 페널티 항이다. \omega는 가중치 벡터이다.)

  • 진화 연산자 (Evolutionary Operators):

  • 교차 (Crossover): 우수한 평가를 받은 두 부모 트리에서 임의의 서브트리(SubTree) 루트를 선택하고, 두 서브트리의 위치를 교환하여 새로운 자식 트리를 생성한다.

  • 돌연변이 (Mutation): 트리의 임의의 위치에 새로운 노드를 삽입하거나, 기존 노드의 유형을 변경(예: Sequence \rightarrow Fallback)하거나, 특정 노드의 파라미터(예: Condition 노드의 임계값)를 연속 확률 분포에 따라 미세 조정(Perturbation)한다.

28.4 시스템 아키텍처 통합 및 파이프라인 명세

이러한 데이터 주도적 합성 알고리즘은 런타임 제어 루프 외부의 오프라인(Offline) 컴퓨팅 노드에서 수행되며, 최종 산출물만 ROS2 실행계층으로 전달된다.

  1. 데이터 수집 계층: rosbag2를 통해 블랙보드 상태와 액션 명령 토픽을 로깅(Logging)한다.
  2. 합성 엔진 (Synthesis Engine): Python 기반의 기계학습 파이프라인(예: scikit-learn, DEAP 프레임워크)이 데이터를 로드하여 의사결정 나무를 생성하고 유전 알고리즘을 수행한다.
  3. XML 생성기 (XML Generator): 진화가 완료되어 수렴된 최적의 추상 구문 트리를 BehaviorTree.CPP가 파싱할 수 있는 표준 XML 포맷으로 직렬화(Serialization)한다.
  4. 검증 및 배포 (Verification & Deployment): (24장에서 명세한) 정형 검증(Formal Verification) 모델 체커를 통해 자동 합성된 트리가 치명적인 논리 결함이나 무한 루프를 발생시키지 않음을 수학적으로 확인한 후, Nav2 bt_navigator 시스템에 동적 로드(Dynamic Load)한다.

이 파이프라인은 복잡한 동적 장애물 회피, 다중 목표 매니퓰레이션과 같이 휴리스틱 기반 트리 설계가 극도로 어려운 태스크에서, 훈련 데이터의 통계적 특성과 시뮬레이션 기반 보상을 통해 수학적으로 검증된 행동 트리를 도출하는 차세대 로봇 소프트웨어 공학의 핵심 명세이다.

29. 로봇 함대 관리 프레임워크(Open-RMF) 및 전역 자원 스케줄링 연동 아키텍처

단일 로봇의 자율 주행(Nav2)을 관장하는 행동 트리(Behavior Tree) 아키텍처를 병원, 물류 창고, 공장 등 다수의 이종(Heterogeneous) 로봇이 혼재된 환경으로 확장하기 위해서는, 로봇 간의 경로 충돌을 방지하고 전역 인프라(엘리베이터, 자동문 등)를 조율하는 상위 관제 시스템과의 통합이 필수적이다. 이를 위해 ROS2 생태계의 표준 함대 관리 프레임워크인 Open-RMF(Robotics Middleware Framework)와 로봇 내장 행동 트리의 상태 동기화 및 전역 자원 스케줄링(Global Resource Scheduling) 아키텍처를 명세한다.

29.1 학술적 배경: 전역 관제(Global Fleet Management)와 지역 자율성(Local Autonomy)의 분리

다중 로봇 환경에서 행동 트리는 단일 에이전트의 지역 자율성을 보장하는 하위 제어 논리로 위치하며, Open-RMF는 전역 교통 관리(Traffic Management)와 작업 분배(Task Dispatching)를 담당하는 상위 관제 논리로 기능한다.

  • 지역 행동 트리 (Local BT): 동적 장애물 회피, 정밀 도킹, 배터리 충전 프로세스 등 센서 데이터에 기반한 실시간 밀착 제어를 수행한다.
  • 전역 스케줄러 (Global Scheduler): 시공간 그래프(Spatiotemporal Graph) 상에서 로봇들의 미래 궤적(Trajectory)을 계산하여 교착 상태(Deadlock)를 예측하고 회피 경로를 산출한다.
  • 함대 어댑터 (Fleet Adapter): Open-RMF 코어와 로봇의 내장 행동 트리 사이를 연결하는 양방향 브리지(Bridge) 모듈이다. 전역 스케줄러의 명령(예: 특정 좌표 대기, 엘리베이터 탑승)을 로봇의 행동 트리가 이해할 수 있는 구체적인 ROS2 액션(Action) 목표로 변환한다.

29.2 전역 자원 선점(Resource Lock)과 상호 배제(Mutual Exclusion) 동기화

로봇이 자동문이나 엘리베이터와 같은 공유 인프라스트럭처를 통과할 때, 행동 트리는 단순 주행 논리를 잠시 중단(Suspend)하고 전역 자원의 상태와 동기화(Synchronization)해야 한다. 이는 운영체제의 세마포어(Semaphore)나 뮤텍스(Mutex) 개념을 물리적 공간에 적용한 것이다.

  • 상태 요청 및 대기 (Request and Wait): 행동 트리는 자동문에 접근하기 전, RequestOpenRMFDoor 액션 노드를 통해 함대 어댑터에 문 개방을 요청하고, 인프라가 완전히 개방될 때까지 RUNNING 상태를 유지하며 트리의 제어 흐름을 블로킹(Blocking)한다.
  • 진입 및 임계 구역(Critical Section) 통과: 문이 개방되어 SUCCESS가 반환되면, 행동 트리는 Sequence 노드에 의해 다음 단계인 FollowPath를 실행하여 좁은 복도나 엘리베이터 내부(임계 구역)로 진입한다.
  • 자원 해제 (Release): 임계 구역 통과가 완료되면 ReleaseOpenRMFDoor 노드를 통해 점유했던 인프라 자원의 락(Lock)을 해제하여 다른 로봇이 사용할 수 있도록 전역 스케줄러에 통보한다.

29.3 교착 상태(Deadlock) 회피와 트래픽 협상(Traffic Negotiation) 메커니즘

두 대의 로봇이 좁은 복도에서 마주치는 병목(Bottleneck) 상황이 발생할 경우, Nav2 행동 트리의 로컬 DWA/TEB 플래너만으로는 상호 회피가 불가능하여 무한 교착 상태에 빠진다. Open-RMF는 트래픽 협상 프로토콜을 통해 이를 해결하며, 행동 트리는 이 협상 결과에 즉각적으로 순응해야 한다.

  1. 충돌 예측: Open-RMF가 각 로봇의 예정된 궤적(Planned Trajectory)을 바탕으로 충돌을 감지한다.
  2. 협상 및 경로 재할당: 스케줄러가 비용 함수(Cost Function)를 계산하여 로봇 A에게는 양보(Yield) 구역으로의 이동을, 로봇 B에게는 직진을 지시하는 새로운 전역 경로를 산출한다.
  3. 컨텍스트 스위칭 (BT Preemption): 함대 어댑터는 로봇 A의 기존 Nav2 MapsToPose 액션을 강제 취소(Cancel)한다. 이로 인해 행동 트리 내부의 실행 중이던 노드는 onHalted()를 호출하며 동작을 멈추고, 즉시 함대 어댑터가 주입한 새로운 회피 경로(Yield Path)를 블랙보드에 갱신하여 주행 서브트리를 재가동한다.

29.4 Open-RMF 연동을 위한 인프라 통과 행동 트리(XML) 명세

다음은 로봇이 Open-RMF의 통제를 받아 자동문이 설치된 좁은 복도를 통과하는 논리를 명세한 행동 트리 아키텍처이다.

<root main_tree_to_execute="OpenRMF_Fleet_Navigation">
    <BehaviorTree ID="OpenRMF_Fleet_Navigation">
        <Sequence name="Navigate_Through_Infrastructure">
            
            <Action ID="NavigateToPose" goal="{door_approach_pose}"/>
            
            <Action ID="OpenRMFDoorClient" 
                    door_name="corridor_door_1" 
                    request_type="OPEN" 
                    timeout="30.0"/>
                    
            <Action ID="FollowPath" path="{through_door_path}"/>
            
            <Action ID="OpenRMFDoorClient" 
                    door_name="corridor_door_1" 
                    request_type="CLOSE" 
                    timeout="5.0"/>
                    
            <Action ID="NavigateToPose" goal="{final_goal_pose}"/>
            
        </Sequence>
    </BehaviorTree>
</root>

29.5 아키텍처의 학술적/공학적 의의

이 연동 아키텍처는 이산 사건 동적 시스템(Discrete Event Dynamic Systems, DEDS)의 제어 이론을 실제 다중 로봇 시스템에 구현한 표준 모델이다. 로봇의 행동 트리(BT)는 복잡한 전역 스케줄링 연산으로부터 해방되어 지역적(Local) 인지 및 물리적 제어 무결성에만 집중할 수 있다. 반면, 교차로 병목 현상이나 인프라 선점과 같은 NP-Hard 급의 스케줄링 문제는 Open-RMF의 클라우드/엣지 서버가 전담하게 함으로써, 수백 대 규모의 로봇 플릿(Fleet)을 운용할 수 있는 엔터프라이즈급 확장성(Scalability)과 시스템 안정성을 논리적으로 보장한다.

30. C++20 코루틴(Coroutine) 및 내장 스크립트(Scripting) 기반 차세대 아키텍처 (BehaviorTree.CPP v4 명세)

BehaviorTree.CPP v3 아키텍처는 로봇 제어 논리를 모듈화하는 데 성공했으나, 단순한 산술 연산이나 불리언(Boolean) 변수 평가를 위해서도 매번 새로운 C++ 클래스(Condition Node)를 작성하고 컴파일해야 하는 경직성(Rigidity)을 내포하고 있었다. 이로 인해 트리의 노드 수가 기하급수적으로 증가하는 노드 비대화(Node Bloat) 현상이 발생했다.

최신 BehaviorTree.CPP v4 아키텍처는 자체적인 추상 구문 트리(Abstract Syntax Tree, AST) 기반의 스크립팅(Scripting) 언어와 코루틴(Coroutine) 기반의 비블로킹(Non-blocking) 제어 흐름을 도입하여, 런타임 유연성을 극대화하고 단일 스레드 틱(Tick) 엔진의 문맥 교환(Context Switching) 오버헤드를 수학적으로 최소화하는 차세대 소프트웨어 공학 명세를 제시한다.

30.1 학술적 배경: 추상화 수준의 향상과 제어 흐름의 경량화

전통적인 ROS2 시스템에서 행동 트리(Behavior Tree)는 100Hz 이상의 고주파수 제어 루프에서 동작한다. 복잡한 제어 분기를 위해 수많은 제어 노드(Sequence, Fallback)가 중첩되면 순회(Traversal) 비용이 증가한다. v4 아키텍처는 이러한 구조적 복잡도를 데이터 계층(블랙보드)의 연산으로 추상화(Abstraction)하여 시간 복잡도를 낮추고 메모리 지역성(Memory Locality)을 향상시킨다.

  • 스크립팅(Scripting): C++ 재컴파일 없이 XML 내부에서 블랙보드 변수 간의 수학적, 논리적 연산을 런타임에 직접 수행한다.
  • 코루틴(Coroutine): OS 수준의 스레드(Thread)를 차단(Blocking)하거나 복잡한 유한 상태 기계(FSM) 클래스를 상속받지 않고도, 함수 실행을 일시 중단(Suspend)하고 제어권을 틱 엔진으로 반환(Yield)한 뒤 다음 주기에 재개(Resume)할 수 있는 언어 수준의 동시성(Concurrency) 모델이다.

30.2 내장 스크립트(Scripting) 및 AST 기반 런타임 평가

v4 아키텍처는 단순한 문자열 파싱(String Parsing)이 아닌, 렉서(Lexer)와 파서(Parser)를 내장하여 스크립트를 컴파일 타임(또는 XML 로드 타임)에 추상 구문 트리(AST)로 변환한다.

  • 실행 최적화: 스크립트는 틱(Tick)이 발생할 때마다 문자열을 해석하는 것이 아니라, 메모리에 미리 구축된 AST 노드 포인터를 순회하며 연산하므로 O(1)에 근접한 상수 시간 실행 속도를 보장한다.
  • 수학적 연산 지원: 할당(:=), 등식/부등식(==, !=, <, >), 산술 연산(+, -, *, /) 및 논리 연산(AND, OR)을 기본적으로 지원하여, 복잡한 기하학적 거리 계산이나 임계값 평가를 XML 내부에서 즉시 처리할 수 있다.

30.3 사전/사후 조건(Pre/Post-conditions)을 통한 트리 토폴로지 최적화

v3에서는 특정 조건을 확인한 후 액션을 실행하려면 반드시 Sequence 노드로 ConditionAction을 묶어야 했다. v4는 모든 노드에 사전 조건(Pre-condition)과 사후 조건(Post-condition) 속성을 주입할 수 있는 메커니즘을 제공하여 트리 토폴로지를 평탄화(Flattening)한다.

  • _skipIf / _failureIf / _successIf (사전 조건): 노드가 틱(Tick)을 수신하기 직전에 스크립트를 평가한다. 조건이 참(True)이면 해당 노드의 C++ 연산을 생략하고 지정된 상태(SKIPPED, FAILURE, SUCCESS)를 즉시 상위로 반환한다.
  • _onSuccess / _onFailure / _onHalted (사후 조건): 노드가 연산을 마치고 특정 상태를 반환할 때 트리거되는 스크립트이다. 주로 블랙보드 내부의 오류 카운터를 증가시키거나 플래그를 초기화하는 데 사용된다.

30.4 코루틴(Coroutine) 기반 비동기 액션 노드의 구조적 진화

비동기 연산을 위해 기존의 BT::StatefulActionNode는 내부 상태를 관리하기 위한 멤버 변수(onStart, onRunning, onHalted 재정의)를 요구했다. 코루틴 기반의 BT::CoroActionNode는 이 구조를 단일 제어 흐름으로 통합한다.

  • 비블로킹 대기 (Non-blocking Yield): 네트워크 응답이나 로봇의 물리적 이동 완료를 대기할 때, 스레드를 잠재우는 std::this_thread::sleep_for 대신 setStatusRunningAndYield() 함수(또는 co_await 패턴)를 호출한다.
  • 문맥 보존 (Context Preservation): 호출 스택(Call Stack)과 지역 변수(Local Variable)의 상태가 코루틴 프레임(Coroutine Frame)에 힙(Heap) 할당되어 안전하게 보존된다. 다음 틱에서 제어권이 돌아오면, 중단되었던 코드 라인에서 정확히 연산을 재개한다.
  • 학술적 이점: 다중 스레드 동기화(Mutex, Lock)에 수반되는 운영체제 커널 계층의 컨텍스트 스위칭 오버헤드를 제거하여 시스템의 실시간(Real-time) 반응성을 극대화한다.

30.5 XML 아키텍처 명세 (v4 문법 적용)

다음은 BehaviorTree.CPP v4의 스크립팅과 조건부 속성을 활용하여, v3 대비 노드 수를 절반 이하로 줄인 로봇 물건 파지(Grasping) 시나리오의 XML 명세이다.

<root main_tree_to_execute="V4_Grasping_Architecture">
    <BehaviorTree ID="V4_Grasping_Architecture">
        
        <Script code=" retry_count := 0 ; max_retries := 3 " />
        
        <Sequence name="Pick_And_Place">
            
            <Action ID="NavigateToPose" 
                    goal="{target_pose}"
                    _skipIf=" distance_to_target < 1.5 "
                    _onSuccess=" is_approached := true "/>
            
            <RetryUntilSuccessful num_attempts="{max_retries}">
                <Sequence>
                    <Action ID="GraspObject"
                            _onFailure=" retry_count := retry_count + 1 "/>
                            
                    <Script code=" grasp_quality := calculateQuality(sensor_data) "/>
                    
                    <Action ID="VerifyGrasp"
                            _failureIf=" grasp_quality <= 0.8 "/>
                </Sequence>
            </RetryUntilSuccessful>
            
        </Sequence>
    </BehaviorTree>
</root>

30.6 아키텍처의 학술적/공학적 의의

이러한 차세대 아키텍처는 제어 논리(Control Logic)와 비즈니스 논리(Business Logic)를 명확히 분리하는 소프트웨어 공학의 단일 책임 원칙(Single Responsibility Principle, SRP)을 고도화한다. C++ 백엔드(Backend) 계층은 오직 하드웨어 I/O 통신과 무거운 수학적 알고리즘 처리에 집중하며, 상태 분기 및 파라미터 연산은 XML 프론트엔드(Frontend) 계층으로 완전히 이관(Delegation)된다. 결과적으로 행동 트리 아키텍처의 재사용성을 극대화하고, 임무 로직의 수정 및 검증 주기를 단축시키는 엔터프라이즈급 로봇 자율화의 표준 설계 패러다임이다.

30.7 C++20 코루틴 도입에 따른 시스템 복잡도 증가 및 한계점

BehaviorTree.CPP v4의 코루틴(Coroutine) 도입은 비동기 액션 노드의 상태 기계(FSM) 보일러플레이트 코드를 축소하는 ’문법적 설탕(Syntactic Sugar)’을 제공하지만, 소프트웨어 공학 및 시스템 프로그래밍 관점에서 실시간 로봇 제어 시스템의 근본적인 복잡도(Complexity)를 급격히 증가시키는 구조적 한계를 지닌다. 이는 안전 필수(Safety-Critical) 환경에서 다음과 같은 아키텍처적 위험 요소를 수반한다.

  • 스택리스(Stackless) 아키텍처와 메모리 수명 주기(Lifecycle) 관리의 난해함: C++20 코루틴은 호출 스택을 유지하지 않고 힙(Heap) 영역에 코루틴 프레임(Coroutine Frame)을 할당한다. co_await를 통한 제어권 반환(Yield) 시점에 참조(Reference)나 포인터로 캡처된 외부 변수가 소멸될 경우, 치명적인 댕글링 참조(Dangling Reference) 및 세그멘테이션 결함(Segmentation Fault)을 유발한다. 이는 메모리 관리의 책임을 프레임워크가 아닌 개발자에게 전가하여 시스템 불안정성을 증대시킨다.
  • 제어 흐름의 단편화 및 가관측성(Observability) 저하: 다중 스레드 환경에서 시스템 충돌(Crash) 발생 시 전통적인 콜 스택(Call Stack) 역추적을 통한 디버깅이 필수적이다. 그러나 코루틴은 실행 중단 후 틱(Tick) 엔진에 의해 상이한 컨텍스트에서 재개(Resume)되므로 콜 스택이 단편화된다. 이는 GDB 등 디버깅 도구를 통한 오류 발원지 추적을 극도로 어렵게 만들어 유지보수 복잡도를 극대화한다.
  • ROS2 실행기(Executor) 모델과의 스케줄링 이질성: ROS2 시스템은 운영체제(OS) 커널 수준의 스레드 풀을 관리하는 MultiThreadedExecutor 기반의 동시성 모델을 채택하고 있다. 반면, 사용자 공간(User Space)에서 동작하는 코루틴의 협력적 멀티태스킹(Cooperative Multitasking) 모델을 기존의 ROS2 비동기 콜백(Callback) 그룹과 동기화하는 과정은 구조적 이질성을 발생시키며, 데이터 경합(Data Race) 및 교착 상태(Deadlock)의 잠재적 위험 표면(Attack Surface)을 넓힌다.
  • 언어적 문법의 높은 인지적 부하(Cognitive Load): 코루틴을 통한 비동기 제어를 메모리 누수 없이 구현하기 위해서는 std::coroutine_handle, promise_type, awaitable 등 C++20의 저수준(Low-level) 비동기 문법에 대한 심층적 이해가 요구된다. 이는 로봇 제어 논리(Control Logic) 구현 자체보다 언어적 복잡성을 해결하는 데 더 많은 엔지니어링 자원을 소모하게 한다.

결론적으로, 행동 트리 아키텍처에서 코루틴 기반의 동시성 구현은 표면적인 코드 길이를 단축시킬 수 있으나, 그 이면에 메모리 관리, 스케줄링 동기화, 디버깅이라는 시스템 프로그래밍 계층의 복잡도를 전가하므로, 신뢰성이 최우선되는 로봇 자율 제어 시스템 설계 시 도입에 신중한 학술적, 공학적 타당성 검토가 요구된다.

31. 시스템 성능 프로파일링 및 최악 실행 시간(WCET) 정량적 지표 분석

안전 필수(Safety-Critical) 로봇 제어 시스템에서 행동 트리(Behavior Tree, BT)는 엄격한 실시간성(Real-Time Constraints)을 충족해야 한다. 틱(Tick) 신호가 루트 노드에서 발생하여 전체 트리를 순회하고 반환되는 총 시간(Tick Latency)은 제어 주파수(Control Frequency)의 역수인 데드라인(Deadline)을 초과해서는 안 된다.

만약 특정 액션 노드나 조건 노드 내부의 연산이 지연되어 스케줄링 기한을 위반(Deadline Miss)할 경우, 로봇의 물리적 제어기(예: 모터 드라이버)에 제어 명령이 적시에 전달되지 않아 시스템의 불안정성(Instability)이나 충돌을 유발한다. 따라서 소프트웨어 아키텍처 설계 단계에서 최악 실행 시간(Worst-Case Execution Time, WCET)을 수학적으로 모델링하고 정량적으로 프로파일링(Profiling)하는 방법론이 필수적으로 요구된다.

31.1 학술적 배경: 시간 복잡도(Time Complexity) 및 최악 실행 시간(WCET) 모델링

행동 트리의 틱 실행 메커니즘은 본질적으로 방향성 비순환 그래프(Directed Acyclic Graph, DAG)의 깊이 우선 탐색(Depth-First Search, DFS)과 수학적으로 동치이다. 시스템의 최악 실행 시간은 트리의 계층적 토폴로지(Topology)와 각 노드의 연산 비용에 의해 결정론적으로 산출된다.

  • 노드 수준의 WCET: 특정 단일 리프 노드(Leaf Node) i가 1회의 틱을 처리하는 데 소요되는 최대 시간을 C_i라고 정의한다.

  • 제어 노드의 시간 복잡도: N개의 자식 노드를 갖는 Sequence 또는 Fallback 노드의 최악 실행 시간은 모든 자식 노드를 순회할 때 발생하므로 다음과 같이 선형 시간 복잡도 O(N)을 갖는다.
    WCET_{Seq} = \sum_{i=1}^{N} C_i + \Delta_{routing}
    (단, \Delta_{routing}은 틱을 자식 노드로 전달하고 상태를 반환받는 데 소요되는 메모리 포인터 접근 및 문맥 교환 오버헤드이다.)

  • 반응형 제어 노드(Reactive Nodes)의 비효율성: ReactiveSequence는 하위 노드가 RUNNING 상태일지라도 매 틱마다 이전 자식 노드들을 재평가(Re-evaluation)하므로, 일반 제어 노드 대비 CPU 사이클 점유율이 높으며 WCET 산출 시 이를 가중치로 반영해야 한다.

시스템의 총 틱 실행 시간 T_{tick}은 반드시 제어 주기 데드라인 D 이하를 만족해야 한다.
T_{tick} \le D

31.2 성능 지표 프로파일링 프레임워크 (LTTng 및 ros2_tracing)

단순히 C++의 std::chrono를 사용하여 틱 시간을 측정하는 방식은 운영체제의 선점(Preemption)이나 인터럽트(Interrupt)로 인한 노이즈를 포함하므로 정밀한 WCET 분석에 부적합하다. ROS2 생태계에서는 커널(Kernel) 수준의 오버헤드를 추적하기 위해 LTTng(Linux Trace Toolkit Next Generation)와 ros2_tracing 계층을 행동 트리에 통합한다.

  • 커널 추적 (Kernel Tracing): 행동 트리 스레드가 스케줄러에 의해 CPU 코어를 할당받고 대기 상태(Sleep)로 전환되는 정확한 타임스탬프(Timestamp)를 나노초(ns) 단위로 기록한다.
  • 사용자 공간 추적 (User-space Tracing, UST): BehaviorTree.CPP 프레임워크는 내부적으로 MinitraceLoggerSQLiteLogger를 제공한다. 이를 ros2_tracing의 이벤트 계측(Instrumentation) 포인트와 결합하여, 틱 신호가 특정 XML 서브트리에 진입하고 이탈하는 순간의 이벤트를 바이너리 데이터로 직렬화(Serialization)한다.

31.3 병목(Bottleneck) 식별 및 플레임 그래프(Flame Graph) 분석

수집된 추적 데이터(Trace Data)는 통계적 분석을 거쳐 시각화 도구를 통해 병목 지점을 식별하는 데 사용된다. 학계 및 산업계에서 표준으로 사용되는 플레임 그래프(Flame Graph)는 행동 트리 내의 연산 점유율을 직관적으로 매핑한다.

  • 구조적 해석: X축은 전체 틱 실행 시간 중 특정 노드가 차지하는 CPU 시간 비율(Percentage)을 나타내며, Y축은 트리의 계층적 호출 스택(Call Stack) 깊이를 의미한다.
  • 병목 노드 식별: 넓은 X축 폭을 차지하는 리프 노드(주로 복잡한 배열 순회를 수행하는 Condition 노드나 비동기 콜백 처리가 지연되는 Action 노드의 onRunning() 메서드)가 시스템의 주요 지터(Jitter) 유발 인자로 지목된다. 이를 통해 엔지니어는 해당 노드의 알고리즘을 최적화하거나 백그라운드 스레드로 연산을 분리하는 아키텍처 리팩토링(Refactoring) 결정을 내릴 수 있다.

31.4 C++ 기반 최악 실행 시간(WCET) 감시 데코레이터 설계 명세

단순한 오프라인 분석을 넘어, 런타임에 특정 노드가 할당된 WCET 예산(Budget)을 초과하는지 실시간으로 감시(Monitor)하고 위반 시 시스템에 경고를 발생시키는 하드 실시간(Hard Real-time) 프로파일링 데코레이터 노드의 C++ 구현 명세이다.

#include <behaviortree_cpp_v3/decorator_node.h>
#include <rclcpp/rclcpp.hpp>
#include <chrono>

// 실행 시간 임계값 위반을 감시하는 커스텀 데코레이터 노드
class WCETMonitorDecorator : public BT::DecoratorNode
{
public:
    WCETMonitorDecorator(const std::string& name, const BT::NodeConfiguration& config)
        : BT::DecoratorNode(name, config)
    {
    }

    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<int>("max_microsec", "Maximum allowed execution time in microseconds") };
    }

    BT::NodeStatus tick() override
    {
        int max_microsec = 0;
        if (!getInput("max_microsec", max_microsec)) {
            throw BT::RuntimeError("Missing [max_microsec] in WCETMonitorDecorator");
        }

        // 고해상도 하드웨어 클럭을 사용한 측정 시작
        auto start_time = std::chrono::high_resolution_clock::now();

        // 자식 노드로 틱(Tick) 전파 및 실행
        setStatus(BT::NodeStatus::RUNNING);
        BT::NodeStatus child_status = child_node_->executeTick();

        // 측정 종료 및 소요 시간 계산
        auto end_time = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();

        // 정량적 지표 평가 및 WCET 위반 감지
        if (duration > max_microsec) {
            // 위반 시 단순히 FAILURE를 반환하지 않고, 프로파일링 시스템에 치명적 경고를 로깅
            // (ROS2 RCLCPP_ERROR 매크로 호출 구조가 주입되었다고 가정)
            fprintf(stderr, "[WCET VIOLATION] Node '%s' exceeded budget! "
                            "Allowed: %d us, Actual: %ld us\n", 
                            child_node_->name().c_str(), max_microsec, duration);
            
            // 시스템 정책에 따라 FAILURE 반환 또는 강제 중단 로직 추가 가능
        }

        return child_status;
    }
};

31.5 아키텍처의 학술적/공학적 의의

정량적 프로파일링 및 WCET 분석 체계는 행동 트리가 단순한 제어 흐름 분기(Branching) 스크립트를 넘어, 예측 가능한(Predictable) 실시간 소프트웨어 아키텍처로 기능함을 수학적으로 보증한다. 시스템 통합(System Integration) 단계에서 LTTng와 플레임 그래프 분석을 통해 도출된 통계적 데이터는, 로봇 운영체제(RTOS/Linux PREEMPT_RT)의 태스크 스케줄링(Task Scheduling) 파라미터를 최적화하고 CPU 코어 친화도(Core Affinity)를 할당하는 하드웨어-소프트웨어 공동 설계(Hardware-Software Co-design)의 절대적 기준점(Baseline)으로 작용한다.

32. SROS2 기반 분산 행동 트리 통신 암호화 및 실시간성 오버헤드 분석 (Cybersecurity and SROS2 Integration)

다중 에이전트 시스템(Multi-Agent System, MAS) 환경에서 분산 행동 트리(Distributed Behavior Trees, DBT)는 네트워크 미들웨어를 통해 틱(Tick) 신호를 위임하고 전역 블랙보드(Global Blackboard) 상태를 동기화한다. 개방형 무선 통신망을 사용하는 로봇 군집 시스템은 패킷 스니핑(Packet Sniffing), 메시지 변조(Tampering), 그리고 비인가 에이전트의 틱 스푸핑(Spoofing) 공격에 취약하다. 이를 방어하기 위해 ROS2의 표준 보안 프레임워크인 SROS2(Secure ROS2)와 DDS-Security 플러그인을 행동 트리 아키텍처에 통합해야 하며, 이때 발생하는 암호화 연산의 실시간성(Real-Time) 지연 오버헤드를 수학적으로 모델링하고 통제해야 한다.

32.1 SROS2 보안 미들웨어 통합 및 엔클레이브(Enclave) 매핑 아키텍처

SROS2는 애플리케이션 계층의 코드(C++) 수정 없이 DDS(Data Distribution Service) 미들웨어 계층에서 통신을 암호화한다. 행동 트리 아키텍처를 SROS2와 통합하기 위해서는 트리의 각 분산 노드가 사용하는 통신 인터페이스를 보안 엔클레이브(Security Enclave)로 매핑하고 엄격한 접근 제어(Access Control)를 설정해야 한다.

  • 인증(Authentication) 및 키 생성: 군집 내의 모든 로봇(리더 및 팔로워)은 고유한 X.509 인증서와 RSA/ECC 개인키를 발급받아 ID를 증명한다.
  • 접근 제어 정책(Access Control Policy): 특정 로봇의 행동 트리만이 특정 전역 블랙보드 토픽에 접근할 수 있도록 permissions.xml을 통해 권한을 분리한다. 예를 들어, 리더 로봇의 원격 액션 노드(Remote Action Node)만이 팔로워 로봇의 /execute_bt 액션 서버에 목표(Goal)를 전송(Publish)할 수 있도록 허용한다.
  • 페이로드 암호화(Cryptographic Payload): 대칭키 암호화(예: AES-GCM)를 사용하여 틱 신호와 블랙보드 데이터의 기밀성(Confidentiality) 및 무결성(Integrity)을 보장한다.

32.2 실시간성(Real-Time) 오버헤드 수학적 모델링

통신 패킷의 암호화 및 복호화 과정은 시스템의 CPU 사이클을 점유하며, 이는 행동 트리의 제어 주파수(Control Frequency)를 저하시키는 주요 원인이 된다. 분산 행동 트리의 최악 실행 시간(Worst-Case Execution Time, WCET)은 보안 오버헤드를 포함하여 재정의되어야 한다.

보안이 적용된 분산 액션 노드 i의 통신 시간 모델 T_{secure_comm}은 다음과 같이 정의된다.
T_{secure\_comm} = T_{network} + T_{crypto\_tx}(S_{payload}) + T_{crypto\_rx}(S_{payload}) + T_{MAC}

  • T_{network}: 물리적 네트워크의 순수 전송 지연 시간
  • T_{crypto\_tx}: 전송 측(리더 로봇)에서 크기가 S_{payload}인 블랙보드 데이터를 AES 알고리즘으로 암호화하는 데 소요되는 시간
  • T_{crypto\_rx}: 수신 측(팔로워 로봇)에서 데이터를 복호화하는 데 소요되는 시간
  • T_{MAC}: 메시지 인증 코드(Message Authentication Code) 연산 및 서명 검증에 소요되는 시간

결과적으로, SROS2 환경에서 분산 행동 트리의 총 틱 실행 시간 T_{tick_secure}는 시스템의 스케줄링 데드라인 D를 반드시 만족해야 한다.
T_{tick\_secure} = \sum_{j \in Local} C_j + \max_{k \in Remote} (C_k + T_{secure\_comm}^k) \le D
암호화로 인해 T_{secure_comm}의 지터(Jitter)가 증가하므로, 행동 트리 설계 시 네트워크 응답을 대기하는 Timeout 데코레이터의 임계값을 T_{crypto}의 분산(Variance)을 고려하여 재설정해야 한다.

32.3 결함 주입(Fault Injection) 방어 및 권한 이탈 제어 명세

SROS2 접근 제어(Access Control) 설정에 따라 분산 행동 트리의 통신 토픽을 제한하는 명세이다. 리더 로봇만이 틱을 위임하고, 팔로워 로봇은 상태 피드백만 전송하도록 permissions.xml을 구성하여 구조적 무결성을 확보한다.

<permissions>
  <grant name="LeaderRobot_BT_Enclave">
    <subject_name>CN=LeaderRobot, O=SwarmFleet</subject_name>
    <validity>
      <not_before>2026-01-01T00:00:00</not_before>
      <not_after>2036-12-31T23:59:59</not_after>
    </validity>
    <allow_rule>
      <publish>
        <topics>
          <topic>rt/swarm/global_blackboard</topic>
        </topics>
      </publish>
      <request>
        <actions>
          <action>rq/follower_a/execute_bt</action>
        </actions>
      </request>
    </allow_rule>
  </grant>

  <grant name="FollowerRobot_BT_Enclave">
    <subject_name>CN=FollowerRobotA, O=SwarmFleet</subject_name>
    <allow_rule>
      <subscribe>
        <topics>
          <topic>rt/swarm/global_blackboard</topic>
        </topics>
      </subscribe>
      <reply>
        <actions>
          <action>rr/follower_a/execute_bt</action>
        </actions>
      </reply>
    </allow_rule>
  </grant>
</permissions>

32.4 보안 지연(Security Latency) 감시 및 QoS 대응 데코레이터 C++ 구현 명세

SROS2 적용 시 암호화/복호화 오버헤드로 인해 DDS의 데드라인(Deadline) QoS를 위반할 확률이 높아진다. 원격 에이전트로부터 틱 상태(SUCCESS/FAILURE) 반환이 보안 연산 병목으로 지연될 경우, 제어 흐름을 즉각적으로 대체 복구 로직으로 전환하는 보안 지연 인지형 데코레이터(Security Latency-Aware Decorator)의 학술적 구현 명세이다.

#include <behaviortree_cpp_v3/decorator_node.h>
#include <rclcpp/rclcpp.hpp>
#include <chrono>

// SROS2 암호화 오버헤드로 인한 원격 틱 지연을 감시하는 데코레이터
class SecureRemoteLatencyMonitor : public BT::DecoratorNode
{
public:
    SecureRemoteLatencyMonitor(const std::string& name, const BT::NodeConfiguration& config)
        : BT::DecoratorNode(name, config)
    {
    }

    static BT::PortsList providedPorts()
    {
        return { 
            BT::InputPort<double>("crypto_timeout_sec", "Maximum allowed latency including crypto overhead") 
        };
    }

    BT::NodeStatus tick() override
    {
        double timeout_sec = 0.0;
        if (!getInput("crypto_timeout_sec", timeout_sec)) {
            throw BT::RuntimeError("Missing [crypto_timeout_sec] input");
        }

        // 1. 틱 전파 전 타임스탬프 기록
        if (status() == BT::NodeStatus::IDLE) {
            start_time_ = std::chrono::steady_clock::now();
        }

        // 2. 자식 노드(주로 RemoteActionNode)로 틱 전파
        setStatus(BT::NodeStatus::RUNNING);
        BT::NodeStatus child_status = child_node_->executeTick();

        // 3. 자식 노드가 RUNNING 상태를 유지하며 원격 응답을 대기하는 경우 지연 검사
        if (child_status == BT::NodeStatus::RUNNING) {
            auto current_time = std::chrono::steady_clock::now();
            std::chrono::duration<double> elapsed = current_time - start_time_;

            // 통신 지연 + 암호화 연산 오버헤드가 허용 임계치를 초과했는지 확인
            if (elapsed.count() > timeout_sec) {
                // SROS2 계층의 병목 또는 패킷 드롭으로 간주하고 자식 노드 실행 강제 중단
                haltChild();
                
                // 로깅 시스템에 보안 연산 타임아웃 경고 기록
                fprintf(stderr, "[SECURITY TIMEOUT] Remote tick validation exceeded %f sec. "
                                "Potential DDoS or cryptographic bottleneck detected.\n", timeout_sec);
                
                // 실패 반환을 통해 상위 트리의 Fallback 복구 로직(예: 지역 자율 제어 전환) 유도
                return BT::NodeStatus::FAILURE;
            }
            return BT::NodeStatus::RUNNING;
        }

        // 자식 노드가 완료된 경우 상태 반환 및 타이머 초기화
        return child_status;
    }

private:
    std::chrono::time_point<std::chrono::steady_clock> start_time_;
};

32.5 학술적 설계 검증:

본 명세는 애플리케이션 계층(행동 트리)과 미들웨어 계층(SROS2)의 역할을 철저히 분리한다. 행동 트리 내에 암호화 알고리즘을 직접 하드코딩하지 않고 DDS-Security 프로파일에 권한을 위임함으로써 코드의 결합도를 낮춘다. 동시에, 암호화로 인해 필연적으로 발생하는 시간적 비결정성(Non-determinism)을 SecureRemoteLatencyMonitor 데코레이터를 통해 시스템 수준에서 통제하여, 보안 기능의 도입이 로봇 군집의 물리적 안전성(Physical Safety)을 저해하지 않도록 수학적, 구조적으로 보장한다.

33. 하드웨어-인-더-루프(HIL) 시뮬레이션 및 CI/CD 자동화 검증 파이프라인 (HIL & CI/CD Pipeline)

로봇 제어 소프트웨어 공학에서 14장(단위 테스트)이 개별 행동 트리(Behavior Tree) 노드의 국소적 논리를 검증하고, 24장(정형 검증)이 시스템의 수학적 무결성을 증명한다면, 32장에서 다루는 통합 검증 파이프라인은 이 둘 사이의 간극을 메우는 실증적(Empirical) 검증 단계이다. 실제 타겟 하드웨어의 자원 제약과 운영체제 스케줄링 특성을 반영한 하드웨어-인-더-루프(Hardware-in-the-Loop, HIL) 시뮬레이션과, 이를 지속적 통합 및 배포(CI/CD) 환경에 자동화하는 아키텍처는 안전 필수(Safety-Critical) 로봇 시스템 배포의 필수 요건이다.

33.1 SIL(Software-in-the-Loop) 및 HIL(Hardware-in-the-Loop) 아키텍처 모델링

행동 트리의 검증은 실행 환경의 물리적 결합도(Coupling)에 따라 두 단계의 루프(Loop) 아키텍처로 설계된다.

  • SIL (Software-in-the-Loop): 행동 트리 실행 엔진(BT Executor)과 물리 엔진 기반 시뮬레이터(예: Gazebo, Isaac Sim)가 동일한 호스트 PC 또는 클라우드 컨테이너(Docker) 내에서 구동된다. ROS2 미들웨어를 통해 센서 데이터와 제어 명령을 교환하며, 시스템 클럭은 use_sim_time 파라미터를 통해 시뮬레이터의 논리적 시간(Logical Time)에 완벽히 동기화(Synchronization)된다. 이는 알고리즘의 순수 논리적 흐름을 검증하는 데 목적이 있다.
  • HIL (Hardware-in-the-Loop): 실제 로봇에 탑재될 온보드 컴퓨터(SBC, 예: NVIDIA Jetson)나 마이크로컨트롤러(MCU) 상에서 행동 트리 C++ 바이너리를 크로스 컴파일(Cross-compile)하여 구동한다. 제어 대상인 모터 동역학이나 환경 센서(LiDAR, Camera) 모델만 고성능 시뮬레이터 PC에서 연산되며, 두 시스템은 이더넷(Ethernet) 또는 CAN 버스를 통해 물리적으로 연결된다.
  • HIL 검증의 학술적 타당성: HIL 환경은 타겟 하드웨어의 실제 CPU 아키텍처(ARM 등), 캐시 메모리 병목, 실시간 운영체제(RTOS/PREEMPT_RT)의 컨텍스트 스위칭 오버헤드를 그대로 노출시킨다. 30장에서 모델링한 틱(Tick) 지연(Latency)과 최악 실행 시간(WCET) 위반 여부를 물리적 배포 이전에 정량적으로 프로파일링할 수 있는 유일한 아키텍처이다.

33.2 무결성 회귀 테스트(Regression Testing) 및 코너 케이스 자동 생성

행동 트리 XML 구조가 변경되거나 C++ 커스텀 노드가 업데이트될 때마다, 기존에 정상 동작하던 로직이 붕괴되는 회귀(Regression) 결함을 방지하기 위한 자동화된 시나리오 테스트가 요구된다. ROS2의 launch_testing 프레임워크를 행동 트리 검증에 통합한다.

  • 결함 주입(Fault Injection): 테스트 스크립트는 런타임에 고의로 센서 토픽의 발행을 중단하거나(Packet Drop), 네트워크 지연을 모사(Network Delay Emulation)하여 행동 트리의 Timeout 데코레이터나 Fallback 노드가 수학적으로 설계된 복구 경로(Recovery Path)로 정확히 전이(Transition)하는지 검증한다.
  • 상태 전이 단언(State Transition Assertion): 시뮬레이션이 진행되는 동안 행동 트리의 특정 서브트리가 SUCCESS 또는 FAILURE를 반환하는 시점을 모니터링하고, 기대되는 이산 사건(Discrete Event) 시퀀스와 실제 실행 로그가 일치하는지 단언(Assert) 연산자로 평가한다.

33.3 지속적 통합(Continuous Integration) 파이프라인 설계 명세

코드 저장소(예: GitHub, GitLab)에 새로운 행동 트리 코드가 푸시(Push)되면, 형상 관리 서버는 격리된 컨테이너 환경에서 일련의 정적/동적 검증 파이프라인을 트리거(Trigger)한다.

  1. 정적 분석 및 린팅(Linting): C++ 소스 코드에 대한 메모리 누수 검사(예: Valgrind, Clang-Tidy)를 수행하고, Groot2 CLI 툴을 헤드리스(Headless) 모드로 호출하여 XML 트리의 포트 매핑(Port Mapping) 오류나 미등록 커스텀 노드 호출 등의 문법적 결함을 컴파일 이전에 차단한다.

  2. 단위 테스트 수행: 14장에서 명세한 GTest 기반의 격리된 노드 테스트를 실행하여 테스트 커버리지(Test Coverage) C를 측정한다.

    C = \frac{T_{executed}}{T_{total}} \times 100 (\%)

    (단, T_{executed}는 실행된 코드 라인 또는 분기 수, T_{total}은 전체 코드 라인 또는 분기 수이다. 안전 필수 시스템의 경우 분기 커버리지(Branch Coverage) 100%를 목표로 한다.)

  3. SIL 기반 시나리오 통합 테스트: 시뮬레이터를 백그라운드 프로세스로 구동하고 로봇의 목적지 도달, 장애물 회피, 예외 상황 복구 등 수십 개의 시나리오를 병렬로 검증한다.

32.4 CI/CD YAML 파이프라인 구현 명세 (GitHub Actions 예시)

다음은 ROS2 Humble 컨테이너 환경에서 행동 트리의 빌드, 정적 XML 검증, 단위 테스트, 그리고 SIL 통합 테스트를 순차적으로 자동화하는 CI/CD 아키텍처의 YAML 명세이다.

name: BehaviorTree_CI_Pipeline

on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build_and_test:
    runs-on: ubuntu-22.04
    container:
      image: osrf/ros:humble-desktop

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Install Dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y ros-humble-behaviortree-cpp-v3 ros-humble-nav2-behavior-tree
          rosdep update
          rosdep install --from-paths src --ignore-src -r -y

      - name: Static XML Validation (Groot2 Headless)
        run: |
          # 등록된 노드 팩토리 목록과 XML 트리를 비교하여 문법 및 포트 매핑 오류 검증
          # (가상의 groot2_cli 검증 스크립트 호출 예시)
          python3 src/bt_validator/scripts/validate_xml.py --tree src/my_robot_bt/trees/main_tree.xml

      - name: Build ROS2 Workspace
        run: |
          source /opt/ros/humble/setup.bash
          colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release

      - name: Run Unit Tests (GTest)
        run: |
          source install/setup.bash
          colcon test --packages-select my_robot_bt
          colcon test-result --all

      - name: Run SIL Integration Test (Launch Testing)
        run: |
          source install/setup.bash
          # 백그라운드에서 Gazebo 및 BT 실행 노드를 띄우고 회귀 테스트 수행
          pytest src/my_robot_bt/test/integration_test_navigation.py

학술적/공학적 의의:

이러한 HIL 및 CI/CD 파이프라인 아키텍처는 행동 트리의 구조적 유연성(XML 수정의 용이성)이 초래할 수 있는 잠재적 시스템 붕괴 위험을 기계적으로 통제한다. 인간 엔지니어의 수동 테스트에 의존하지 않고, 모든 코드와 논리의 변경 사항이 수학적, 논리적, 그리고 물리적 제약 조건(시간/메모리) 하에서 회귀 결함이 없음을 정량적으로 증명한 후에만 실제 하드웨어에 바이너리가 배포되도록 강제하는 현대 로봇 소프트웨어 공학의 핵심 인프라이다.

33. 에너지 인지형(Energy-Aware) 행동 트리 및 동적 전력 관리(DPM) 아키텍처

제한된 배터리 용량을 지닌 모바일 로봇, 무인 항공기(UAV), 해저 탐사 로봇 등의 자율 제어 시스템에서 에너지는 하드 제약(Hard Constraint)으로 작용한다. 기존의 행동 트리(Behavior Tree)가 주로 논리적 상태(State)와 공간적 제약(Spatial Constraint)에 기반하여 제어 흐름을 분기했다면, 에너지 인지형 행동 트리는 각 작업(Task)의 예상 전력 소모량과 잔여 배터리 상태를 결합하여 시스템의 작동 시간(Operational Time)을 최적화하는 동적 전력 관리(Dynamic Power Management, DPM) 아키텍처를 명세한다.

33.1 수학적 비용 함수 및 전력 소비 모델링 (Mathematical Power Consumption Model)

행동 트리의 제어 흐름을 에너지 관점에서 최적화하기 위해, 각 리프 노드(Action Node)가 실행되는 동안 소비하는 에너지를 수학적으로 추정해야 한다. 특정 노드 i가 시간 t 동안 실행될 때 요구되는 전력 P_i(t)와 총 소모 에너지 E_i는 다음과 같이 모델링된다.
E_i = \int_{0}^{T_{exec}} P_i(t) dt \approx \bar{P}_i \cdot T_{exec}

  • T_{exec}: 해당 액션 노드의 예상 최악 실행 시간(WCET) 또는 평균 실행 시간.
  • \bar{P}_i: 모터 구동, 센서 스캐닝, 컴퓨팅(CPU/GPU 연산) 부하를 합산한 노드 i의 평균 전력 소모량.

트리의 특정 서브트리(SubTree) S를 순회(Traversal)하여 임무를 완수하는 데 필요한 총 예상 에너지 E_{req}(S)는 포함된 액션 노드들의 에너지 합으로 추산된다. 에너지 인지형 행동 트리는 다음의 하드 제약을 충족하는 서브트리만을 틱(Tick)하도록 강제된다.
E_{req}(S) + E_{margin} \le E_{battery\_remaining}
(단, E_{margin}은 베이스 캠프 귀환이나 긴급 제동을 위해 예약된 안전 여유 전력량이며, E_{battery_remaining}은 현재 배터리의 가용 에너지이다.)

33.2 동적 조건 분기 및 로컬 폴백(Fallback) 설계 패턴

전력 소비 모델에 기반하여, 행동 트리는 잔여 에너지가 특정 임무를 수행하기에 불충분하다고 평가될 경우, 즉각적으로 저전력 대안 행동(Low-Power Alternative)으로 컨텍스트 스위칭(Context Switching)을 수행하는 폴백(Fallback) 구조를 갖는다.

  • 에너지 평가 조건 노드 (Energy Evaluation Condition Node): 블랙보드(Blackboard)에 주기적으로 기록되는 배터리 잔량 토픽(예: sensor_msgs/msg/BatteryState)을 읽어, 다음 액션에 필요한 E_{req}(S)와 비교한다. 조건 충족 시 SUCCESS를 반환하여 고성능 작업을 허용하고, 불충족 시 FAILURE를 반환하여 저전력 폴백 논리로 제어권을 넘긴다.
  • 비용-성능 트레이드오프 분기 (Cost-Performance Trade-off Branching):
    • 고전력/고정밀 서브트리 (Primary): 3D LiDAR 기반 VSLAM, GPU 가속 기반 객체 인식, 고속 주행 등 높은 전력을 소모하지만 임무 달성 확률이 높은 경로.
    • 저전력/저정밀 서브트리 (Alternative): 2D LiDAR 기반 맵 매칭, CPU 기반 단순 휴리스틱 인식, 저속 주행 등 정밀도는 낮으나 전력 소모가 극도로 적은 경로.

33.3 ROS2 운영체제 전력 관리 노드와의 연동 (Integration with DVFS)

행동 트리는 소프트웨어 논리 분기뿐만 아니라, 운영체제 수준의 하드웨어 전력 관리 기법인 동적 전압 및 주파수 스케일링(Dynamic Voltage and Frequency Scaling, DVFS)과 연동되어야 한다.

  • 하드웨어 프로필 제어 (Hardware Profile Control): 행동 트리의 특정 액션 노드(예: SetCPUProfile)가 ROS2 서비스 호출을 통해 온보드 컴퓨터(SBC)의 전력 모드를 제어한다. 예를 들어, 저전력 서브트리로 진입할 때 시스템 주파수를 강제로 낮추고 사용하지 않는 센서 모듈(예: RGB-D 카메라)의 전원을 차단(Power Gating)하는 명령을 하달한다.
  • 비동기 전원 복구 대기 (Asynchronous Power Wake-up): 절전 모드에 진입해 있던 센서를 다시 가동할 경우, 센서의 부팅 및 안정화 시간(Wake-up Time)이 필요하다. 액션 노드는 이 시간 동안 RUNNING 상태를 반환하여, 틱 엔진의 블로킹(Blocking) 없이 시스템 클럭 동기화를 보장한다.

33.4 에너지 인지형 행동 트리 XML 구현 명세

다음은 배터리 잔량을 평가하여 고전력 3D 내비게이션과 저전력 2D 내비게이션 사이에서 동적으로 제어 흐름을 분기하는 에너지 인지형 아키텍처의 XML 명세이다.

<root main_tree_to_execute="EnergyAwareNavigation">
    <BehaviorTree ID="EnergyAwareNavigation">
        <Sequence name="Mission_Execution">
            
            <Action ID="UpdateBatteryState" output_battery="{current_battery_level}"/>
            
            <Fallback name="Adaptive_Navigation_Strategy">
                
                <Sequence name="HighPower_3D_Navigation">
                    <Condition ID="IsEnergySufficient" 
                               current_level="{current_battery_level}" 
                               required_energy="40.0"/>
                    
                    <Action ID="SetHardwareProfile" profile="MAX_PERFORMANCE"/>
                    <Action ID="Run3DVSLAM" goal="{target_pose}"/>
                </Sequence>
                
                <Sequence name="LowPower_2D_Navigation">
                    <Condition ID="IsEnergySufficient" 
                               current_level="{current_battery_level}" 
                               required_energy="15.0"/>
                    
                    <Action ID="SetHardwareProfile" profile="POWER_SAVING"/>
                    <Action ID="Run2DNav" goal="{target_pose}"/>
                </Sequence>
                
                <Sequence name="Emergency_Survival">
                    <Action ID="SetHardwareProfile" profile="DEEP_SLEEP"/>
                    <Action ID="BroadcastSOS" />
                    <Action ID="HaltSystem" />
                </Sequence>

            </Fallback>
            
        </Sequence>
    </BehaviorTree>
</root>

학술적/공학적 의의:

이 명세는 에너지 고갈로 인한 시스템 셧다운(Shutdown)이라는 치명적인 실패(Catastrophic Failure)를 행동 트리의 계층적 폴백 논리로 방어하는 아키텍처이다. 소프트웨어의 이산적 논리 평가와 운영체제의 물리적 전력 제어(DVFS)를 결합함으로써, 자율 로봇 시스템의 생존성(Survivability)과 임무 가용성(Availability)을 수학적, 구조적으로 극대화한다.

34. 설명 가능성(Explainability) 확보를 위한 의미론적 사후 감사(Semantic Post-audit) 프레임워크

자율 로봇 시스템에 기계학습(ML) 및 강화학습(RL) 정책이 융합(22장 참조)되고 임무의 복잡도가 증가함에 따라, 로봇의 예기치 않은 오작동이나 임무 실패 시 그 원인을 규명하는 작업은 법적 책임(Accountability) 및 시스템 인증(Certification)의 핵심 요건이 되었다. 기존의 바이너리 상태 로깅(Binary State Logging)은 단편적인 노드의 성공/실패 여부만을 기록할 뿐, ‘왜(Why)’ 그러한 논리적 분기가 발생했는지에 대한 인과관계(Causality)를 제공하지 못한다.

따라서 행동 트리(Behavior Tree) 아키텍처 내에 설명 가능한 인공지능(Explainable AI, XAI) 개념을 도입하여, 이산 사건(Discrete Event)의 계층적 연쇄와 당시의 환경 문맥(Context)을 인간이 이해하고 기계가 분석할 수 있는 온톨로지(Ontology) 형태로 추출하는 의미론적 사후 감사(Semantic Post-audit) 프레임워크를 명세한다.

34.1 학술적 배경: 설명 가능한 로봇 공학과 역방향 연쇄(Backward Chaining) 추론

행동 트리는 방향성 비순환 그래프(Directed Acyclic Graph, DAG) 구조를 가지므로, 루트(Root) 노드에서 발생한 최종 FAILURE 상태는 반드시 하위 자식 노드들로부터 전파(Propagation)된 논리적 결과물이다. 사후 감사 프레임워크는 이 특성을 활용하여 역방향 연쇄(Backward Chaining) 추론을 수행한다.

실행 궤적(Execution Trace) \mathcal{T}는 틱(Tick) 발생 시점 t에서의 노드 상태 변이 집합으로 정의된다. 특정 시점 t_{error}에서 시스템 실패가 선언되었을 때, 인과적 실패 경로(Causal Failure Path) P_{fail}은 루트 노드 n_{root}부터 실제 실패를 유발한 리프 노드(Leaf Node) n_{leaf}까지의 연결 노드로 수학적으로 구성된다.
P_{fail} = \{ n_{root}, n_1, \dots, n_k, n_{leaf} \}
이 경로 상에 위치한 제어 노드(예: Sequence, Fallback)의 라우팅 정책을 역추적하면, 시스템이 어떤 대안 행동(Alternative Action)들을 시도했으나 모두 기각되었는지를 결정론적으로(Deterministically) 증명할 수 있다.

34.2 온톨로지(Ontology) 기반 런타임 상태 스냅샷(State Snapshot)

단순히 노드의 이름과 상태 변이(RUNNING \rightarrow FAILURE)만을 기록하는 것은 의미론적 해석에 한계가 있다. 행동 트리의 노드는 블랙보드(Blackboard)에 저장된 데이터에 의존하여 결정을 내리므로, 상태 변이가 발생하는 정확한 시점의 블랙보드 스냅샷(Snapshot)을 변이 이벤트와 결합하여 온톨로지를 구성해야 한다.

  • 인과적 매핑(Causal Mapping): 조건 노드(Condition Node)가 FAILURE를 반환할 때, 해당 연산에 사용된 블랙보드의 특정 키(Key)와 값(Value)을 로깅 페이로드(Payload)에 포함시킨다. (예: IsBatterySufficient 노드 실패 시, 당시의 current_battery_level = 10.5V 값을 함께 캡처)
  • 의미론적 태깅(Semantic Tagging): 각 커스텀 BT 노드에 메타데이터(Metadata) 태그를 부여한다. (예: [Category: Safety], [Category: Perception]). 이를 통해 사후 분석 시 “안전 계층의 조건 불충족으로 인한 주행 중단“과 같이 고수준의 자연어 설명(Natural Language Explanation)을 자동 생성할 수 있다.

34.3 C++ 기반 의미론적 로거(Semantic Logger) 구현 명세

BehaviorTree.CPP 프레임워크가 제공하는 BT::StatusChangeLogger 인터페이스를 확장하여, 상태 변화 시점의 블랙보드 문맥을 캡처하고 JSON 포맷으로 직렬화(Serialization)하는 커스텀 사후 감사 로거의 아키텍처이다.

#include <behaviortree_cpp_v3/loggers/abstract_logger.h>
#include <behaviortree_cpp_v3/behavior_tree.h>
#include <fstream>
#include <nlohmann/json.hpp> // JSON 처리를 위한 라이브러리

class SemanticAuditLogger : public BT::StatusChangeLogger
{
public:
    SemanticAuditLogger(BT::Tree& tree, const std::string& output_file)
        : BT::StatusChangeLogger(tree.rootNode()), tree_(tree)
    {
        out_stream_.open(output_file);
        audit_log_ = nlohmann::json::array();
    }

    ~SemanticAuditLogger() override
    {
        // 소멸 시 최종 JSON 배열을 디스크에 기록
        out_stream_ << audit_log_.dump(4);
        out_stream_.close();
    }

    // 노드의 상태가 변경될 때마다 프레임워크에 의해 콜백 호출됨
    void callback(BT::Duration timestamp, const BT::TreeNode& node, 
                  BT::NodeStatus prev_status, BT::NodeStatus status) override
    {
        // 유의미한 상태 변이(완료 또는 실패)만 기록
        if (status == BT::NodeStatus::IDLE || status == BT::NodeStatus::RUNNING) {
            return;
        }

        nlohmann::json event;
        event["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp).count();
        event["node_name"] = node.name();
        event["node_type"] = node.registrationName();
        event["status"] = (status == BT::NodeStatus::SUCCESS) ? "SUCCESS" : "FAILURE";

        // 실패 발생 시, 블랙보드의 주요 상태(Context) 스냅샷 캡처
        if (status == BT::NodeStatus::FAILURE) {
            nlohmann::json context;
            
            // 예시: 해당 노드가 참조하는 블랙보드 포트 값을 추출
            for (const auto& port : node.config().input_ports) {
                std::string port_key = port.first;
                // 블랙보드에서 해당 포트의 값을 문자열로 변환하여 추출 (구현 생략)
                std::string port_value = tree_.rootNode()->config().blackboard->get<std::string>(port_key);
                context[port_key] = port_value;
            }
            event["context_snapshot"] = context;
        }

        audit_log_.push_back(event);
    }

    void flush() override { out_stream_.flush(); }

private:
    BT::Tree& tree_;
    std::ofstream out_stream_;
    nlohmann::json audit_log_;
};

34.4 사후 감사용 표준 JSON 출력 명세 및 해석

위 로거를 통해 추출된 의미론적 실행 궤적(Semantic Execution Trace)은 다음과 같은 JSON 구조를 갖는다. 이를 통해 분석 자동화 스크립트는 로봇의 실패 원인을 계층적으로 재구성할 수 있다.

[
    {
        "timestamp": 1679845201000,
        "node_name": "CheckCollisionRisk",
        "node_type": "ConditionNode",
        "status": "FAILURE",
        "context_snapshot": {
            "fused_risk_level": "0.95",
            "threshold": "0.80"
        }
    },
    {
        "timestamp": 1679845201005,
        "node_name": "EmergencyHandling",
        "node_type": "ReactiveSequence",
        "status": "FAILURE"
    },
    {
        "timestamp": 1679845201015,
        "node_name": "PriorityController",
        "node_type": "ReactiveFallback",
        "status": "SUCCESS" 
    }
]

해석(Explanation) 도출 논리: 위 로그는 역방향 연쇄 추론기를 통해 다음과 같은 인간 가독형(Human-readable) 설명으로 변환된다.

“로봇의 주 임무가 중단된 원인은 CheckCollisionRisk 노드에서 평가된 fused_risk_level(0.95)이 허용 임계치(threshold: 0.80)를 초과했기 때문이며, 이로 인해 EmergencyHandling 시퀀스가 실패하고, 상위의 PriorityController 폴백 노드에 의해 우선순위가 높은 대체 회피 기동으로 제어권이 전환되었습니다.”

34.5 아키텍처의 학술적/공학적 의의

의미론적 사후 감사 프레임워크는 행동 트리 기반 로봇 제어 시스템을 ’화이트박스(White-box)’화 하는 핵심 기술이다. 이는 단순히 버그를 수정하는 디버깅(Debugging) 용도를 넘어선다. 특히 의료 로봇, 자율주행 차량, 방산 분야와 같이 사고 발생 시 엄격한 사고 조사(Incident Investigation)가 요구되는 도메인에서, 자율 시스템이 내린 의사결정의 논리적 정당성(Logical Justification)과 당시의 환경적 맥락(Contextual Variables)을 객관적 데이터로 입증(Prove)하는 강력한 소프트웨어 공학적 수단을 제공한다.

35. 하이브리드 오토마타(Hybrid Automata) 기반 연속-이산 통합 상태 공간 모델링

35. 하이브리드 오토마타(Hybrid Automata) 기반 연속-이산 통합 상태 공간 모델링

행동 트리(Behavior Tree, BT)는 본질적으로 이산 사건(Discrete Event) 시스템으로, 노드의 상태 전이(SUCCESS, FAILURE, RUNNING)를 통해 논리적 제어 흐름을 분기한다. 반면, 로봇의 물리적 구동(모터 제어, 동역학)은 미분 방정식으로 기술되는 연속 시간(Continuous Time) 동적 시스템이다. 이 두 가지 이질적인 상태 공간을 분리하여 설계할 경우, 경계 조건에서의 잦은 상태 전환(Chattering)이나 물리적 불안정성이 발생할 수 있다.

이러한 한계를 수학적으로 극복하기 위해, 행동 트리의 이산적 제어 논리와 로봇의 연속적 물리 동역학을 단일 프레임워크로 통합하는 하이브리드 오토마타(Hybrid Automata) 모델링 명세를 제시한다.

35.1 행동 트리의 하이브리드 오토마타 수학적 정의 (Mathematical Definition)

행동 트리 제어 시스템을 내장한 로봇은 수학적으로 하이브리드 오토마타 H = (Q, X, Init, f, Dom, E, G, R)로 치환하여 정형화할 수 있다.

  • Q (이산 상태 집합, Discrete States): 행동 트리에서 현재 RUNNING 상태를 반환하며 활성화된 액션 노드(Action Node)들의 조합이다. (예: q_1 = \text{FollowPath}, q_2 = \text{EmergencyBrake})
  • X (연속 상태 공간, Continuous State Space): 로봇의 물리적 상태 변수 집합이다. X \subseteq \mathbb{R}^n이며, 위치, 속도, 가속도 등을 포함한다.
  • Init (초기 상태, Initial States): Init \subseteq Q \times X로, 시스템이 시작될 때의 논리적 노드와 물리적 상태의 부분집합이다.
  • f (벡터장, Vector Fields): 각 이산 상태 q \in Q에서 로봇의 물리적 거동을 지배하는 연속 동역학(미분 방정식)이다. \dot{x} = f(q, x)로 표현된다.
  • Dom (도메인, Domain / Invariant): 시스템이 특정 이산 상태 q에 머무를 수 있는 연속 상태 x의 조건이다. 행동 트리의 ReactiveSequence 등에서 선행 조건(Pre-condition) 노드가 참을 유지하는 영역이다.
  • E (전이, Edges/Transitions): 행동 트리의 틱(Tick) 신호 라우팅에 의해 한 액션 노드에서 다른 액션 노드로 제어권이 넘어가는 논리적 분기이다. E \subseteq Q \times Q.
  • G (가드, Guards): 이산 상태 전이 e = (q, q') \in E를 유발하는 조건이다. G(e) \subseteq X이며, 행동 트리의 조건 노드(Condition Node)가 특정한 물리적 상태 x를 평가하여 SUCCESS 또는 FAILURE를 반환하는 임계 영역을 수학적으로 정의한다.
  • R (리셋 맵, Reset Map): 전이 발생 시 연속 상태 x가 불연속적으로 변환되는 규칙이다. 로봇의 경우 운동량 보존 등에 의해 보통 항등 함수(Identity Function) R(e, x) = {x}로 정의된다.

35.2 제노 현상(Zeno Behavior) 방지 및 체류 시간(Dwell Time) 설계

행동 트리의 반응형 제어 노드(예: ReactiveFallback)는 매 틱마다 조건을 재평가한다. 만약 로봇의 상태 x가 가드 G(e)의 경계 부근에서 미세하게 진동할 경우, 유한한 시간 내에 무한번의 이산 상태 전이가 발생하는 제노 현상(Zeno Behavior)이 유발된다. 이는 로봇 제어기의 엑추에이터 포화(Actuator Saturation) 및 치명적인 하드웨어 손상을 초래한다.

제노 현상을 수학적으로 방지하기 위해 두 가지 공학적 제약 조건이 시스템에 도입되어야 한다.

  1. 최소 체류 시간 (Minimum Dwell Time, \tau_{dwell}): 시스템이 새로운 이산 상태 q'로 전이한 후, 다음 전이가 허용될 때까지 반드시 머물러야 하는 최소 물리적 시간이다. 이는 행동 트리의 데코레이터(Decorator) 노드인 KeepRunningUntilTime 패턴으로 구현된다.
  2. 이력 현상 (Hysteresis) 기반 가드 설계: 가드 조건 G(e)에 단일 임계값(Threshold)이 아닌, 상태 진입 임계값과 이탈 임계값 사이에 마진(Margin)을 두어 상태 전환의 불감대(Deadzone)를 형성한다.

35.3 제어 장벽 함수(CBF)와 행동 트리 가드(Guard)의 결합

안전 필수(Safety-Critical) 로봇 제어에서, 특정 행동 노드의 도메인 Dom(q)을 이탈하여 위험 영역으로 진입하는 것을 방지하기 위해 제어 장벽 함수(Control Barrier Functions, CBF)를 가드 G(e)로 편입한다.

안전 집합(Safe Set) \mathcal{C}가 연속 미분 가능한 함수 h(x)에 의해 \mathcal{C} = {x \in \mathbb{R}^n \mid h(x) \ge 0}로 정의될 때, 하위 제어기는 다음의 CBF 부등식을 만족해야 한다.
\dot{h}(x) + \alpha(h(x)) \ge 0
행동 트리의 조건 노드는 로봇의 현재 상태 x와 예상 궤적이 위 부등식을 위반할 임계점에 도달했는지(즉, \dot{h}(x) + \alpha(h(x)) < \epsilon)를 평가한다. 이 수학적 평가 결과가 가드 G(e)로 작용하여, FAILURE를 반환함과 동시에 Fallback 노드를 통해 즉각적인 충돌 회피(Collision Avoidance) 이산 상태로 제어권을 전이(Transition)시킨다.

35.4 C++ 구현 명세: 이력 현상 기반 조건 노드 (Hysteresis Condition Node)

제노 현상 방지를 위해 상태 전환 불감대를 적용한 조건 노드의 C++ 설계 명세이다. 센서 노이즈가 존재하는 연속 시간 물리량(예: 장애물까지의 거리)을 이산 논리로 안전하게 사상(Mapping)한다.

#include <behaviortree_cpp_v3/condition_node.h>

class HysteresisConditionNode : public BT::ConditionNode
{
public:
    HysteresisConditionNode(const std::string& name, const BT::NodeConfiguration& config)
        : BT::ConditionNode(name, config), previous_state_(false)
    {
    }

    static BT::PortsList providedPorts()
    {
        return {
            BT::InputPort<double>("sensor_value", "Continuous sensor value (e.g., distance)"),
            BT::InputPort<double>("high_threshold", "Threshold to trigger SUCCESS (Activation)"),
            BT::InputPort<double>("low_threshold", "Threshold to trigger FAILURE (Deactivation)")
        };
    }

    BT::NodeStatus tick() override
    {
        double sensor_value = 0.0;
        double high_threshold = 0.0;
        double low_threshold = 0.0;

        if (!getInput("sensor_value", sensor_value) ||
            !getInput("high_threshold", high_threshold) ||
            !getInput("low_threshold", low_threshold)) 
        {
            throw BT::RuntimeError("Missing required port data in HysteresisConditionNode");
        }

        // 논리적 무결성 검증: high_threshold는 반드시 low_threshold보다 커야 함 (마진 확보)
        if (high_threshold <= low_threshold) {
            throw BT::LogicError("High threshold must be strictly greater than low threshold");
        }

        // 이력 현상(Hysteresis) 기반 논리 평가
        if (sensor_value >= high_threshold) {
            previous_state_ = true;
            return BT::NodeStatus::SUCCESS;
        } 
        else if (sensor_value <= low_threshold) {
            previous_state_ = false;
            return BT::NodeStatus::FAILURE;
        } 
        else {
            // 히스테리시스 구간 (low_threshold < sensor_value < high_threshold) 내에서는
            // 이전의 논리 상태를 그대로 유지하여 Chattering(잦은 상태 변이)을 방지함.
            return previous_state_ ? BT::NodeStatus::SUCCESS : BT::NodeStatus::FAILURE;
        }
    }

private:
    bool previous_state_; // 시스템의 이전 이산 상태를 기억하기 위한 내부 변수
};

35.5 학술적 및 공학적 의의

하이브리드 오토마타 기반의 모델링은 소프트웨어 공학의 ’이산 논리(Discrete Logic)’와 제어 공학의 ‘연속 동역학(Continuous Dynamics)’ 사이의 이론적 간극을 결합한다. 행동 트리 아키텍처에 이 프레임워크를 적용함으로써, 단순히 노드가 순서대로 실행되는 스크립트 수준을 넘어 시스템 전체의 리야프노프 안정성(Lyapunov Stability)과 전방 불변성(Forward Invariance, 안전 영역 이탈 방지)을 수학적으로 증명할 수 있는 견고한 제어 아키텍처를 확립할 수 있다.

36. 해석적 다중화(Analytical Redundancy) 및 고장 감지·격리(FDI) 알고리즘 통합

36. 해석적 다중화(Analytical Redundancy) 및 고장 감지·격리(FDI) 알고리즘 통합

로봇 자율 제어 시스템에서 센서나 액추에이터의 결함은 치명적인 물리적 사고로 직결된다. 이를 방지하기 위해 동일한 하드웨어를 여러 개 장착하는 물리적 다중화(Physical Redundancy) 기법이 존재하나, 이는 시스템의 비용, 무게, 전력 소모를 증가시키는 한계가 있다. 이에 대한 현대 제어 공학의 대안은 시스템의 수학적 동역학 모델(Mathematical Dynamics Model)을 활용하여 가상의 센서 데이터를 추정하고, 이를 실제 측정치와 비교하여 결함을 진단하는 해석적 다중화(Analytical Redundancy) 기법이다.

이 장에서는 확장 칼만 필터(EKF)나 상태 관측기(State Observer)를 통한 고장 감지 및 격리(Fault Detection and Isolation, FDI) 알고리즘을 행동 트리(Behavior Tree, BT) 아키텍처에 통합하여, 결함 발생 시 시스템을 결정론적으로 재구성(Reconfiguration)하는 결함 허용 제어(Fault-Tolerant Control) 명세를 기술한다.

36.1 학술적 배경: 잔차(Residual) 생성 및 통계적 고장 감지 모델링

FDI 알고리즘의 핵심은 시스템에 고장이 없을 때 0에 수렴하고, 고장 발생 시 0이 아닌 값으로 편향되는 잔차(Residual) 신호 r(t)를 수학적으로 생성하는 것이다.

로봇의 이산 시간(Discrete-time) 동역학 및 관측 모델이 다음과 같이 주어질 때:
x_{k+1} = f(x_k, u_k) + w_k

y_k = h(x_k) + v_k + f_k

(단, x_k는 상태 벡터, u_k는 제어 입력, y_k는 센서 측정치, w_k, v_k는 시스템 및 관측 노이즈, f_k는 발생한 고장(Fault) 벡터를 의미한다.)

상태 관측기를 통해 추정된 출력 \hat{y}_k를 바탕으로 잔차 r_k를 도출한다.
r_k = y_k - \hat{y}_k
단순한 임계값 비교는 센서 노이즈 v_k로 인한 오경보(False Alarm)를 유발하므로, 누적합(Cumulative Sum, CUSUM) 알고리즘과 같은 통계적 검정 기법을 적용하여 잔차의 평균 편이(Mean Shift)를 감지한다. 통계량 S_k는 다음과 같이 재귀적으로 연산된다.
S_k = \max(0, S_{k-1} + |r_k| - \mu)
행동 트리의 조건 노드는 이 통계량 S_k가 사전 정의된 임계값 \tau를 초과하는지(S_k > \tau) 지속적으로 평가하여 고장 발생 여부를 논리적 상태(SUCCESS/FAILURE)로 변환한다.

36.2 행동 트리를 통한 FDI 진단 논리의 계층화 및 격리(Isolation)

고장이 감지(Detection)된 후, 어떤 센서나 하추에이터에서 문제가 발생했는지 식별하는 격리(Isolation) 과정이 수반되어야 한다. 행동 트리 아키텍처에서는 다중 모델(Multiple Models) 접근법을 병렬 노드(Parallel Node)와 결합하여 이를 구현한다.

  • 병렬 잔차 평가 (Parallel Residual Evaluation): 각 센서 채널(예: LiDAR, Vision, Wheel Odometry)에 대응하는 독립적인 관측기 모델을 백그라운드 ROS2 노드로 구동하고, 행동 트리의 Parallel 노드 하위에 위치한 여러 개의 FDI 조건 노드가 각 채널의 잔차를 동시에 모니터링한다.
  • 고장 서명 행렬 (Fault Signature Matrix): 특정 잔차들의 조합이 임계값을 초과하는 패턴(Signature)을 분석하여 고장난 구성 요소를 논리적으로 추론한다.
  • 동적 재구성 (Dynamic Reconfiguration): 고장 격리가 완료되면, 해당 센서의 데이터를 융합에서 제외하거나, Fallback 노드를 통해 정상 작동하는 이종 센서 기반의 예비 제어 서브트리(Degraded Mode SubTree)로 제어권을 즉각 이양한다.

36.3 C++ 구현 명세: 통계적 잔차 평가 기반 FDI 조건 노드

ROS2 퍼셉션 노드에서 발행되는 잔차 데이터를 구독하고, 내부적으로 CUSUM 알고리즘을 연산하여 통계적 유의성이 확인될 경우 FAILURE(고장 상태)를 반환하는 커스텀 FDI 조건 노드의 C++ 명세이다.

#include <behaviortree_cpp_v3/condition_node.h>
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/float64.hpp>
#include <mutex>
#include <cmath>

class ResidualFDI_ConditionNode : public BT::ConditionNode
{
public:
    ResidualFDI_ConditionNode(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node::SharedPtr ros_node)
        : BT::ConditionNode(name, config), ros_node_(ros_node), cusum_statistic_(0.0)
    {
        // ROS2 잔차(Residual) 데이터 구독 설정
        residual_sub_ = ros_node_->create_subscription<std_msgs::msg::Float64>(
            "/fdi/residual/lidar", 10,
            std::bind(&ResidualFDI_ConditionNode::residualCallback, this, std::placeholders::_1));
    }

    static BT::PortsList providedPorts()
    {
        return {
            BT::InputPort<double>("drift_mu", "Drift parameter for CUSUM algorithm"),
            BT::InputPort<double>("threshold_tau", "Threshold for fault detection limit")
        };
    }

    BT::NodeStatus tick() override
    {
        double tau = 0.0;
        if (!getInput("threshold_tau", tau)) {
            throw BT::RuntimeError("Missing [threshold_tau] input");
        }

        double current_cusum;
        {
            std::scoped_lock<std::mutex> lock(data_mutex_);
            current_cusum = cusum_statistic_;
        }

        // CUSUM 통계량이 임계값을 초과하면 고장(FAULT)으로 간주하여 FAILURE 반환
        if (current_cusum > tau) {
            RCLCPP_ERROR(ros_node_->get_logger(), "[FDI ALARM] Sensor fault isolated! CUSUM: %f > %f", current_cusum, tau);
            return BT::NodeStatus::FAILURE; 
        }

        // 정상 상태일 경우 SUCCESS 반환하여 주 임무 서브트리 유지 허용
        return BT::NodeStatus::SUCCESS;
    }

private:
    void residualCallback(const std_msgs::msg::Float64::SharedPtr msg)
    {
        double mu = 0.0;
        getInput("drift_mu", mu);

        std::scoped_lock<std::mutex> lock(data_mutex_);
        
        // CUSUM 재귀 연산: S_k = max(0, S_{k-1} + |r_k| - mu)
        cusum_statistic_ = std::max(0.0, cusum_statistic_ + std::abs(msg->data) - mu);
    }

    rclcpp::Node::SharedPtr ros_node_;
    rclcpp::Subscription<std_msgs::msg::Float64>::SharedPtr residual_sub_;
    
    std::mutex data_mutex_;
    double cusum_statistic_;C++
};

36.4 XML 기반 동적 재구성(Reconfiguration) 폴백 아키텍처

위에서 구현된 FDI 조건 노드를 ReactiveFallback과 결합하여, 주 센서(LiDAR) 고장 시 예비 센서(Vision) 기반의 저하 모드(Degraded Mode)로 자율 전환하는 시스템 아키텍처 명세이다.

<root main_tree_to_execute="FaultTolerantNavigation">
    <BehaviorTree ID="FaultTolerantNavigation">
        <ReactiveFallback name="FDI_Reconfiguration_Controller">
            
            <ReactiveSequence name="Primary_LiDAR_Nav">
                <Condition ID="ResidualFDI_ConditionNode" 
                           drift_mu="0.5" 
                           threshold_tau="5.0" />
                
                <Action ID="ComputePathToPose" goal="{goal_pose}" planner_id="LiDAR_Planner"/>
                <Action ID="FollowPath" path="{path}"/>
            </ReactiveSequence>
            
            <ReactiveSequence name="Secondary_Vision_Nav">
                <Condition ID="ResidualFDI_ConditionNode" 
                           drift_mu="0.5" 
                           threshold_tau="5.0" />
                
                <Action ID="ComputePathToPose" goal="{goal_pose}" planner_id="Vision_Planner"/>
                <Action ID="FollowPath" path="{path}"/>
            </ReactiveSequence>
            
            <Sequence name="Emergency_Safe_Stop">
                <Action ID="ApplyEmergencyBrake" />
                <Action ID="BroadcastFaultAlarm" />
            </Sequence>
            
        </ReactiveFallback>
    </BehaviorTree>
</root>

36.5 아키텍처의 학술적/공학적 의의

이 통합 아키텍처는 고장 진단의 복잡한 수학적 필터링 연산(상태 관측기 및 통계적 검정)을 백그라운드 ROS2 계층으로 완전히 은닉(Encapsulation)하고, 행동 트리는 오직 도출된 논리적 결과(SUCCESS 또는 FAILURE)에 따른 이산적 의사결정(Decision Making)에만 집중하도록 시스템 관심사를 분리(Separation of Concerns)한다. 이를 통해 물리적 하드웨어의 추가 없이 소프트웨어 모델링만으로 로봇 시스템의 가용성(Availability)과 결함 허용성(Fault Tolerance)을 극대화하며, 예기치 않은 하드웨어 열화 상황에서도 결정론적인 페일세이프(Failsafe) 메커니즘을 수학적으로 보장한다.

37. 시간 민감형 네트워크(TSN) 및 DDS QoS 기반 분산 BT 확정성 보장

37. 시간 민감형 네트워크(TSN) 및 DDS QoS 기반 분산 BT 확정성 보장

다중 에이전트 시스템(Multi-Agent System, MAS)에서 분산 행동 트리(Distributed Behavior Trees, DBT)는 네트워크를 통해 틱(Tick) 신호를 위임하고 제어 상태를 동기화한다. 그러나 표준 이더넷(Ethernet)이나 무선 네트워크(Wi-Fi) 환경은 패킷 충돌, 스위치 큐잉 지연, 대역폭 경합으로 인해 시간적 비결정성(Non-determinism)을 내포한다. 이는 하드 실시간(Hard Real-time) 로봇 제어에서 치명적인 데드라인 위반(Deadline Miss)을 초래하므로, 네트워크 계층의 시간 민감형 네트워크(Time-Sensitive Networking, TSN)와 미들웨어 계층의 DDS QoS(Quality of Service) 정책을 통합하여 통신 지연의 상한선(Upper Bound)을 수학적으로 보장해야 한다.

37.1 IEEE 802.1Qbv TSN 기반 MAC 계층 스케줄링

행동 트리의 제어 패킷(틱 위임, SUCCESS/FAILURE 상태 반환)은 크기가 작지만 지연에 극도로 민감한 제어 트래픽(Control Traffic)이다. 반면, 라이다(LiDAR)나 카메라 영상 데이터는 크기가 크고 대역폭을 많이 차지하는 최선력(Best-Effort) 트래픽이다.

이러한 이종 트래픽 간의 간섭을 물리적으로 차단하기 위해 IEEE 802.1Qbv 표준인 시간 인식 스케줄러(Time-Aware Shaper, TAS)를 네트워크 스위치와 로봇의 MAC 계층에 적용한다.

  • 트래픽 클래스 분리: 행동 트리 제어 패킷은 가장 높은 우선순위의 VLAN 태그(예: Priority 7)를 할당받아 전용 하드웨어 큐(Queue)에 적재된다.
  • 게이트 제어 목록 (Gate Control List, GCL): 스위치의 각 출력 포트는 시간에 따라 개폐(Open/Close)되는 게이트를 갖는다. 로봇의 행동 트리 틱 주파수(예: 100Hz, 주기는 10ms)에 동기화하여, 제어 패킷이 전송되는 특정 타임 슬롯(Time Slot) 동안에는 대용량 센서 데이터의 큐 게이트를 강제로 닫아(Close)버린다. 이를 통해 스위치 내의 큐잉 지연(Queueing Delay)을 제거하고 제어 패킷의 전송 지연을 마이크로초(\mu s) 단위의 확정적 수치로 고정한다.

37.2 DDS QoS 프로필과 틱(Tick) 주파수의 수학적 동기화

TSN이 물리적 전송 시간을 보장한다면, ROS2의 기반 미들웨어인 DDS(Data Distribution Service)는 소프트웨어 계층의 상태 동기화를 보장해야 한다. 행동 트리의 생명주기 요구사항을 DDS QoS 파라미터로 매핑(Mapping)하는 모델링은 다음과 같다.

  1. 데드라인 (Deadline QoS): 행동 트리의 틱 주파수가 f_{tick}일 때, 상태 갱신 주기는 T_{tick} = 1/f_{tick}이다. 분산 트리의 수신 측 로봇은 DDS Deadline을 T_{tick} + \Delta_{margin}으로 설정한다. 만약 네트워크 단절이나 원격 로봇의 CPU 과부하로 인해 지정된 데드라인 내에 상태 패킷이 수신되지 않으면, 미들웨어 계층에서 RequestedDeadlineMissed 이벤트를 즉각 발생시켜 행동 트리가 블로킹(Blocking) 없이 FAILURE를 반환하도록 유도한다.
  2. 활성 상태 (Liveliness QoS): AUTOMATIC 대신 MANUAL_BY_TOPIC 정책을 적용한다. 원격 로봇의 행동 트리가 다운되지 않고 물리적으로 정상 연산 중임을 증명하는 심박수(Heartbeat) 메커니즘으로 작동하며, lease_duration을 초과하여 틱 응답이 없을 경우 노드 사망(Node Death)으로 간주하고 로컬 폴백(Fallback)을 트리거한다.
  3. 신뢰성 및 히스토리 (Reliability & History QoS): 제어 명령의 유실을 방지하기 위해 RELIABLE 모드를 사용하되, 재전송(Retransmission) 오버헤드가 제어 주기를 초과하여 과거의 낡은 제어 명령이 누적 실행되는 것을 방지하기 위해 History 정책을 KEEP_LAST, Depth = 1로 제한한다.

37.3 지터 보상 및 적응형 타임아웃(Adaptive Timeout) 데코레이터

통신 왕복 시간(Round Trip Time, RTT)의 분산, 즉 지터(Jitter)로 인해 상태 반환이 일시적으로 지연되는 현상을 통제하기 위해 행동 트리에 지수 가중 이동 평균(EWMA) 기반의 타임아웃 데코레이터를 설계한다.

네트워크 왕복 시간 RTT_k에 대한 평균 추정치 SRTT와 지터 추정치 RTTVAR는 다음과 같이 갱신된다.
SRTT_{k} = (1 - \alpha) \cdot SRTT_{k-1} + \alpha \cdot RTT_k

RTTVAR_{k} = (1 - \beta) \cdot RTTVAR_{k-1} + \beta \cdot |SRTT_{k-1} - RTT_k|

(일반적으로 \alpha = 0.125, \beta = 0.25 적용)

동적 타임아웃 임계값 T_{timeout}SRTT_k + 4 \cdot RTTVAR_k로 설정되어, 일시적인 네트워크 노이즈로 인한 불필요한 임무 취소(False Alarm)를 방지하면서도 실질적인 통신 두절에는 즉각적으로 반응하도록 수학적으로 조율된다.

37.4 C++ 기반 확정적 원격 액션 노드(Deterministic Remote Action Node) 설계 명세

ROS2의 rclcpp::QoS 객체를 행동 트리 노드에 주입하여, 데드라인 및 Liveliness 제약 조건을 준수하는 분산 액션 노드의 구현 명세이다.

#include <behaviortree_cpp_v3/action_node.h>
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

class DeterministicRemoteAction : public BT::StatefulActionNode
{
public:
    DeterministicRemoteAction(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node::SharedPtr ros_node)
        : BT::StatefulActionNode(name, config), ros_node_(ros_node), is_state_updated_(false)
    {
        double tick_period_ms = 100.0; // 기본 10Hz
        getInput("tick_period_ms", tick_period_ms);

        // 1. DDS QoS 프로필을 행동 트리 틱 제약조건과 수학적으로 동기화
        rclcpp::QoS qos_profile(1); // KEEP_LAST, Depth = 1
        qos_profile.reliable();
        
        // Deadline 설정: 틱 주기 + 20% 마진 허용
        auto deadline_duration = std::chrono::milliseconds(static_cast<int>(tick_period_ms * 1.2));
        qos_profile.deadline(deadline_duration);
        
        // Liveliness 설정: 시스템 생존 증명
        qos_profile.liveliness(RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC);
        qos_profile.liveliness_lease_duration(std::chrono::milliseconds(500));

        // 2. 데드라인 위반 이벤트(Deadline Missed) 콜백 등록
        rclcpp::SubscriptionEventCallbacks event_callbacks;
        event_callbacks.deadline_callback = [this](rclcpp::QoSDeadlineRequestedInfo&%20info) {
            RCLCPP_ERROR(this->ros_node_->get_logger(), 
                         "[QoS ALARM] Remote tick deadline missed! Total count: %d", info.total_count);
            // 플래그를 통해 행동 트리에 즉각적인 FAILURE 유도
            this->qos_deadline_missed_ = true;
        };

        // 3. 옵션을 적용한 서브스크라이버 생성
        rclcpp::SubscriptionOptions sub_options;
        sub_options.event_callbacks = event_callbacks;

        remote_status_sub_ = ros_node_->create_subscription<std_msgs::msg::String>(
            "/remote_agent/bt_status", qos_profile,
            std::bind(&DeterministicRemoteAction::statusCallback, this, std::placeholders::_1),
            sub_options);
    }

    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<double>("tick_period_ms", "Expected tick frequency of the remote node") };
    }

    BT::NodeStatus onStart() override
    {
        qos_deadline_missed_ = false;
        is_state_updated_ = false;
        
        // 원격 노드로 작업 시작 명령 전송 (구현 생략)
        return BT::NodeStatus::RUNNING;
    }

    BT::NodeStatus onRunning() override
    {
        // DDS 미들웨어에서 데드라인 위반을 보고한 경우, 블로킹 없이 즉시 폴백(FAILURE) 처리
        if (qos_deadline_missed_) {
            return BT::NodeStatus::FAILURE;
        }

        if (is_state_updated_) {
            is_state_updated_ = false;
            return remote_status_; // 원격 노드에서 계산된 SUCCESS 또는 FAILURE 반환
        }

        return BT::NodeStatus::RUNNING;
    }

    void onHalted() override
    {
        // 중단 시 원격 노드에 취소 명령 브로드캐스트 (구현 생략)
    }

private:
    void statusCallback(const std_msgs::msg::String::SharedPtr msg)
    {
        if (msg->data == "SUCCESS") remote_status_ = BT::NodeStatus::SUCCESS;
        else if (msg->data == "FAILURE") remote_status_ = BT::NodeStatus::FAILURE;
        else remote_status_ = BT::NodeStatus::RUNNING;
        
        is_state_updated_ = true;
    }

    rclcpp::Node::SharedPtr ros_node_;
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr remote_status_sub_;
    
    std::atomic<bool> is_state_updated_;
    std::atomic<bool> qos_deadline_missed_;
    BT::NodeStatus remote_status_;
};

37.5 아키텍처의 학술적/공학적 의의

본 명세는 행동 트리의 논리적 제어 계층(Application Layer)과 이더넷의 물리적 전송 계층(MAC/Network Layer)을 수직적으로 통합하는 전-스택(Full-Stack) 확정성 보장 아키텍처이다. TSN을 통한 패킷 충돌 원천 차단과 DDS QoS를 통한 미들웨어 수준의 하드 데드라인 제어를 결합함으로써, 분산 컴퓨팅 환경에서 로봇 군집 시스템이 단일 로봇과 수학적으로 동일한 시간 결정성(Time Determinism)을 유지하며 안전 필수(Safety-Critical) 임무를 완수할 수 있도록 보증한다.

38. 공유 자율성(Shared Autonomy) 및 혼합 주도권(Mixed-Initiative) BT 설계

원격 조작자(Human Operator)의 개입과 로봇의 자율적 제어 시스템이 동일한 물리적 액추에이터를 동시에 제어해야 하는 환경(예: 수술 로봇, 재난 구조 로봇, 반자율주행 차량)에서는 인간의 인지적 유연성(Cognitive Flexibility)과 로봇의 고속 반응성(High-speed Reactivity)을 안전하게 융합하는 제어 아키텍처가 필수적이다. 행동 트리(Behavior Tree)는 이러한 공유 자율성(Shared Autonomy) 및 혼합 주도권(Mixed-Initiative) 환경에서 인간의 제어 명령과 자율 주행 알고리즘의 출력을 수학적으로 조정(Arbitration)하고, 치명적인 오작동을 하드웨어 도달 이전에 차단하는 논리적 게이트웨이(Gateway) 역할을 수행한다.

38.1 수학적 입력 혼합 및 주도권 할당 모델링 (Mathematical Input Blending)

공유 자율성 시스템에서 최종 제어 입력(Control Input)은 인간의 원격 조작 명령과 자율 알고리즘의 산출물 간의 선형 또는 비선형 결합으로 정의된다. 행동 트리의 특정 액션 노드(Action Node)는 이 두 벡터 공간을 동적으로 병합(Blending)하는 연산을 수행한다.

인간의 제어 입력 벡터를 u_h, 자율 제어기의 입력 벡터를 u_a라고 할 때, 로봇에 인가되는 최종 제어 입력 u_c는 주도권 가중치(Initiative Weight) \alpha \in [0, 1]에 의해 다음과 같이 모델링된다.u_c = \alpha \cdot u_h + (1 - \alpha) \cdot u_a
u_c = \alpha \cdot u_h + (1 - \alpha) \cdot u_a
여기서 가중치 \alpha는 정적인 상수가 아니며, 행동 트리의 블랙보드(Blackboard)에 기록된 환경의 위험도(Risk Level) 및 인간 명령의 신뢰도에 따라 실시간으로 변동하는 매개변수이다. 예를 들어, 로봇이 장애물에 근접하여 제어 장벽 함수(CBF) 기반의 충돌 위험도 \rho(x)가 증가할 경우, 행동 트리의 조건 노드는 \alpha를 0에 수렴시키고 자율 제어기의 회피 기동 가중치 (1 - \alpha)를 극대화하도록 블랙보드 상태를 갱신한다.

38.2 포섭 아키텍처(Subsumption Architecture) 기반 오버라이드(Override) 제어

인간 조작자가 통신 지연, 인지적 착각, 또는 악의적 목적으로 시스템의 물리적 한계를 초과하는 명령을 내릴 경우, 행동 트리는 이를 즉각적으로 무효화(Override)해야 한다. 이는 ReactiveFallbackReactiveSequence 제어 노드를 활용한 포섭(Subsumption) 구조로 명세된다.

  • 최상위 안전 억제 (Safety Inhibition): ReactiveSequence 노드의 첫 번째 자식으로 로봇의 물리적 한계(운동학적 제약, 충돌 불가 영역)를 평가하는 조건 노드를 배치한다. 인간의 명령 u_h가 이 조건을 위반하는 궤적을 생성할 것으로 수학적으로 예측되면, 조건 노드는 즉각 FAILURE를 반환하여 인간의 제어 흐름을 강제 중단(Halt)시킨다.
  • 자율 개입 (Autonomous Intervention): 인간의 제어 흐름이 차단되면, 상위의 ReactiveFallback 노드에 의해 제어권이 예비 자율 제어 서브트리(예: 자동 제동, 충돌 회피 궤적 생성)로 즉각 이양(Transfer)된다.

38.3 지연 내성(Delay-Tolerant) 텔레오퍼레이션 아키텍처

심해 탐사나 우주 로봇과 같이 지상 관제소와의 네트워크 통신 지연(Network Latency) \tau가 수 초에서 수 분에 달하는 환경에서는, 조이스틱을 통한 연속 제어(Continuous Control)가 제어 이론적으로 불가능하다.

  • 제어 주기의 디커플링 (Decoupling of Control Loops): 행동 트리 아키텍처는 고주파수(예: 100Hz)의 국소 자율 제어 루프를 로봇 온보드(On-board) 컴퓨터에서 독립적으로 실행한다.
  • 기호적 의도(Symbolic Intent) 수신: 조작자는 조향 각도(\omega)나 선속도(v)와 같은 저수준 제어 입력 대신, “특정 객체 파지(Grasp Object)” 또는 “웨이포인트 A로 이동“과 같은 고수준의 기호적 목표(Goal)만을 전송한다.
  • 비동기 목표 갱신: 행동 트리의 수신 노드는 네트워크 지연을 겪고 도착한 조작자의 목표를 블랙보드에 비동기적으로 갱신하며, 국소 행동 트리는 통신이 단절된 상황에서도 로컬 센서를 바탕으로 생존성(Survivability)을 자체적으로 보장하며 해당 목표를 추종한다.

38.4 C++ 구현 명세: 혼합 주도권 블렌딩 액션 노드

조작자의 cmd_vel 토픽과 자율 내비게이션 스택의 cmd_vel 토픽을 구독하고, 레이저 스캐너(LiDAR) 기반의 최소 거리 데이터에 비례하여 동적으로 가중치 \alpha를 조정하여 최종 속도 명령을 발행하는 커스텀 액션 노드의 C++ 명세이다.

#include <behaviortree_cpp_v3/action_node.h>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/twist.hpp>
#include <algorithm>

class MixedInitiativeBlendingNode : public BT::ActionNodeBase
{
public:
    MixedInitiativeBlendingNode(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node::SharedPtr ros_node)
        : BT::ActionNodeBase(name, config), ros_node_(ros_node)
    {
        cmd_pub_ = ros_node_->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel_out", 10);
    }

    static BT::PortsList providedPorts()
    {
        return {
            BT::InputPort<geometry_msgs::msg::Twist>("human_cmd", "Velocity command from teleoperator"),
            BT::InputPort<geometry_msgs::msg::Twist>("auto_cmd", "Velocity command from autonomous planner"),
            BT::InputPort<double>("min_obstacle_distance", "Closest obstacle distance in meters"),
            BT::InputPort<double>("critical_distance", "Distance at which human autonomy alpha becomes 0")
        };
    }

    void halt() override
    {
        // 중단 시 정지 명령 발행
        geometry_msgs::msg::Twist stop_msg;
        cmd_pub_->publish(stop_msg);
    }

    BT::NodeStatus tick() override
    {
        geometry_msgs::msg::Twist human_cmd, auto_cmd;
        double min_dist = 0.0;
        double critical_dist = 0.5;

        if (!getInput("human_cmd", human_cmd) || !getInput("auto_cmd", auto_cmd) || 
            !getInput("min_obstacle_distance", min_dist)) 
        {
            throw BT::RuntimeError("Missing required input ports for blending");
        }
        getInput("critical_distance", critical_dist);

        // 안전 거리에 따른 주도권 가중치 alpha 산출 (선형 보간)
        // 거리가 critical_dist 이하일 경우 alpha = 0 (인간 제어권 박탈)
        // 거리가 critical_dist * 3 이상일 경우 alpha = 1 (인간 완전 제어)
        double safe_dist = critical_dist * 3.0;
        double alpha = (min_dist - critical_dist) / (safe_dist - critical_dist);
        alpha = std::clamp(alpha, 0.0, 1.0);

        // 제어 벡터 혼합 연산
        geometry_msgs::msg::Twist blended_cmd;
        blended_cmd.linear.x = alpha * human_cmd.linear.x + (1.0 - alpha) * auto_cmd.linear.x;
        blended_cmd.angular.z = alpha * human_cmd.angular.z + (1.0 - alpha) * auto_cmd.angular.z;

        cmd_pub_->publish(blended_cmd);

        return BT::NodeStatus::SUCCESS;
    }

private:
    rclcpp::Node::SharedPtr ros_node_;
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_pub_;
};

38.5 XML 기반 공유 자율성 및 포섭 아키텍처 명세

위의 혼합 노드와 포섭 구조를 결합하여, 조작자의 입력이 시스템의 안전 경계를 침범할 경우 즉각적으로 자율 제동 시스템이 오버라이드(Override)하는 논리를 기술한 XML 아키텍처이다.

<root main_tree_to_execute="SharedAutonomyArchitecture">
    <BehaviorTree ID="SharedAutonomyArchitecture">
        <ReactiveFallback name="Subsumption_Override_Controller">
            
            <ReactiveSequence name="Emergency_Override">
                <Inverter>
                    <Condition ID="IsObstacleImminent" 
                               min_distance="{lidar_min_dist}" 
                               threshold="0.3"/>
                </Inverter>
                <Action ID="EmergencyBrake" />
            </ReactiveSequence>

            <Sequence name="Nominal_Blended_Teleoperation">
                <Condition ID="CheckNetworkLatency" max_latency_ms="500"/>
                
                <Action ID="MixedInitiativeBlendingNode" 
                        human_cmd="{joy_cmd_vel}" 
                        auto_cmd="{nav_cmd_vel}" 
                        min_obstacle_distance="{lidar_min_dist}"
                        critical_distance="0.5"/>
            </Sequence>

            <Sequence name="Autonomous_Survival">
                <Action ID="ComputePathToPose" goal="{home_base_pose}" path="{safe_path}"/>
                <Action ID="FollowPath" path="{safe_path}"/>
            </Sequence>

        </ReactiveFallback>
    </BehaviorTree>
</root>

38.6 아키텍처의 학술적/공학적 의의

이 명세는 제어 권한(Control Authority)의 100% 이양이라는 극단적인 이분법적 접근(Manual vs. Autonomous)을 탈피하여, 수학적 가중치 행렬과 논리적 이산 사건 평가를 결합한 유연한 스펙트럼의 자율성을 로봇에 부여한다. 특히 인간의 오판이나 네트워크 대기 시간(Latency)이라는 본질적인 외부 불확실성을 행동 트리의 계층적 트래핑(Trapping) 메커니즘을 통해 소프트웨어적으로 흡수함으로써, 복합적인 인간-로봇 상호작용(HRI) 상황에서도 로봇의 물리적 불변성(Invariance)과 시스템 안정성을 결정론적으로 보증한다.

39. 확률론적 행동 트리 (Probabilistic Behavior Trees, PBT) 및 부분 관측 불확실성 모델링

전통적인 행동 트리(Behavior Tree, BT)는 센서 데이터와 환경 상태를 결정론적(Deterministic)으로 신뢰한다는 가정하에 설계되며, 조건 평가는 이분법적인 이산 상태(SUCCESS 또는 FAILURE)만을 반환한다. 그러나 실제 로봇이 구동되는 물리적 환경은 센서 노이즈(Noise), 가려짐(Occlusion), 측정 한계로 인해 불확실성(Uncertainty)이 내포되어 있다. 확률론적 행동 트리(Probabilistic Behavior Trees, PBT)는 이러한 부분 관측성(Partial Observability)을 수학적으로 모델링하여, 확정된 스칼라 값이 아닌 확률 분포(Probability Distribution)를 기반으로 제어 흐름을 동적으로 라우팅(Routing)하는 심화 아키텍처이다.

39.1 부분 관측 마르코프 결정 과정(POMDP)과 신뢰 상태(Belief State) 통합

행동 트리가 불확실성을 다루기 위해서는 시스템의 기반 모델을 부분 관측 마르코프 결정 과정(Partially Observable Markov Decision Process, POMDP)으로 확장해야 한다.

POMDP는 튜플 \langle S, A, T, R, \Omega, O \rangle로 정의된다. 로봇은 환경의 실제 상태 s \in S를 직접 관측할 수 없으며, 오직 관측치 o \in \Omega를 통해 상태를 추정한다. 이에 따라 행동 트리의 블랙보드(Blackboard)는 단일 변수 값이 아닌 신뢰 상태(Belief State) b(s)를 유지한다. b(s)는 로봇이 현재 상태 s에 있을 확률 분포를 나타낸다.

특정 액션 a를 수행하고 관측치 o를 얻었을 때, 행동 트리의 상태 동기화 노드는 베이즈 정리(Bayes’ Theorem)에 기반하여 다음 틱(Tick) 주기 이전에 신뢰 상태를 수학적으로 갱신(Update)한다.
b'(s') = \eta O(o | s', a) \sum_{s \in S} T(s' | s, a) b(s)
(단, T(s' | s, a)는 상태 전이 확률, O(o | s', a)는 관측 확률, \eta는 정규화 상수이다.)

행동 트리의 조건 노드(Condition Node)는 추정된 상태 벡터의 분산(Variance)이나 기댓값(Expected Value)을 평가하여, 특정 가설의 신뢰도가 임계치(예: 95% 신뢰 구간)를 초과할 때만 SUCCESS를 반환하도록 설계된다.

39.2 확률적 제어 노드 (Probabilistic Control Nodes)의 수학적 라우팅

결정론적 Fallback (또는 Selector) 노드는 자식 노드들을 항상 왼쪽에서 오른쪽으로 정해진 순서대로 틱(Tick)한다. 반면, ProbabilisticFallback 노드는 각 자식 액션이 성공할 사전 확률(Prior Probability) P(SUCCESS | Action_i)을 블랙보드나 노드 파라미터로부터 동적으로 읽어들여 라우팅 순서를 재배열한다.

  • 동적 정렬 알고리즘 (Dynamic Sorting): 매 틱마다 자식 노드 집합 {C_1, C_2, \dots, C_n}을 성공 확률 p_i의 내림차순으로 정렬하여, 통계적으로 가장 성공 가능성이 높은 서브트리부터 우선적으로 탐색한다.

  • 효용 함수 (Utility Function) 기반 최적화: 액션의 실행 비용(Cost) c_i가 상이할 경우, 단순 확률이 아닌 기대 효용(Expected Utility) U_i를 최대화하는 순서로 분기한다.
    U_i = p_i \cdot V_{success} - (1 - p_i) \cdot V_{penalty} - c_i
    (단, V_{success}는 성공 시 보상, V_{penalty}는 실패 시의 위험 페널티이다.)

39.3 베이즈 업데이트 기반 조건 노드 C++ 구현 명세

입력되는 데이터가 스칼라 값이 아닌 공분산(Covariance) 행렬을 포함하는 가우시안 분포(Gaussian Distribution)일 때, 불확실성이 특정 임계값 이하로 수렴했는지 평가하는 C++ 커스텀 조건 노드 명세이다.

#include <behaviortree_cpp_v3/condition_node.h>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/pose_with_covariance_stamped.hpp>
#include <Eigen/Dense>

class BeliefStateCondition : public BT::ConditionNode
{
public:
    BeliefStateCondition(const std::string& name, const BT::NodeConfiguration& config)
        : BT::ConditionNode(name, config)
    {
    }

    static BT::PortsList providedPorts()
    {
        return {
            BT::InputPort<geometry_msgs::msg::PoseWithCovarianceStamped>("belief_pose", "Current belief state of robot pose"),
            BT::InputPort<double>("max_uncertainty_entropy", "Maximum allowed information entropy")
        };
    }

    BT::NodeStatus tick() override
    {
        geometry_msgs::msg::PoseWithCovarianceStamped belief_msg;
        double max_entropy = 0.0;

        if (!getInput("belief_pose", belief_msg) || !getInput("max_uncertainty_entropy", max_entropy)) {
            throw BT::RuntimeError("Missing belief state or threshold parameters");
        }

        // 6x6 공분산 행렬 추출 (x, y, z, roll, pitch, yaw)
        Eigen::MatrixXd cov_matrix(6, 6);
        for (int i = 0; i < 6; ++i) {
            for (int j = 0; j < 6; ++j) {
                cov_matrix(i, j) = belief_msg.pose.covariance[i * 6 + j];
            }
        }

        // 정보 엔트로피(Information Entropy) H 계산 (다변량 정규 분포 가정)
        // H = 0.5 * ln(|2 * pi * e * Sigma|)
        double determinant = cov_matrix.determinant();
        
        // 행렬식이 0 이하인 경우 (수학적 특이점), 불확실성이 무한대로 간주
        if (determinant <= 0.0) {
            return BT::NodeStatus::FAILURE;
        }

        double entropy = 0.5 * std::log(std::pow(2.0 * M_PI * M_E, 6.0) * determinant);

        // 엔트로피(불확실성)가 임계값보다 낮아야만(즉, 확신도가 높아야만) 논리적 참(SUCCESS) 반환
        if (entropy <= max_entropy) {
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::FAILURE;
    }
};

39.4 XML 기반 확률론적 회피 아키텍처 명세

로봇이 장애물을 회피할 때, 왼쪽 우회와 오른쪽 우회의 과거 성공 이력(블랙보드에 유지되는 확률 변수)을 바탕으로 동적으로 탐색 순서를 결정하는 ProbabilisticFallback 구조이다.

<root main_tree_to_execute="ProbabilisticAvoidance">
    <BehaviorTree ID="ProbabilisticAvoidance">
        <Sequence name="Navigate_With_Uncertainty">
            
            <Condition ID="BeliefStateCondition" 
                       belief_pose="{amcl_pose}" 
                       max_uncertainty_entropy="2.5"/>
            
            <ProbabilisticFallback name="Adaptive_Obstacle_Bypass">
                
                <Sequence name="Bypass_Left" success_probability="{prob_left_bypass}">
                    <Action ID="ComputePathToPose" goal="{left_waypoint}"/>
                    <Action ID="FollowPath"/>
                </Sequence>

                <Sequence name="Bypass_Right" success_probability="{prob_right_bypass}">
                    <Action ID="ComputePathToPose" goal="{right_waypoint}"/>
                    <Action ID="FollowPath"/>
                </Sequence>
                
                <Sequence name="Wait_For_Dynamic_Clear" success_probability="0.1">
                    <Action ID="Delay" delay_msec="5000"/>
                </Sequence>

            </ProbabilisticFallback>

        </Sequence>
    </BehaviorTree>
</root>

39.5 아키텍처의 학술적/공학적 의의

확률론적 행동 트리(PBT)는 인공지능 분야의 마르코프 의사결정 프레임워크와 소프트웨어 공학의 반응형 제어 아키텍처를 결합한 모델이다. 이는 로봇이 센서 노이즈나 일시적인 가려짐(Occlusion) 앞에서도 하드코딩된 예외 처리 로직에 의존하는 대신, 통계적 확신도(Statistical Confidence)가 수렴할 때까지 능동적으로 정보를 수집(Active Perception)하거나, 수학적 기댓값이 가장 높은 복구 행동을 자율적으로 선택하도록 보장한다.

40. 지식 그래프(Knowledge Graph) 및 온톨로지(Ontology) 기반 의미론적 행동 트리 (Semantic BT)

로봇이 수행할 임무의 복잡도가 증가하고 활동 반경이 비정형(Unstructured) 환경으로 확장됨에 따라, 단순한 키-값(Key-Value) 쌍에 의존하는 전통적인 행동 트리(Behavior Tree)의 블랙보드(Blackboard) 아키텍처는 한계에 직면한다. 로봇은 객체 간의 관계(Relations), 계층적 속성(Hierarchical Properties), 그리고 물리적 제약조건을 논리적으로 추론(Reasoning)할 수 있어야 한다.

의미론적 행동 트리(Semantic Behavior Tree)는 데이터베이스를 지식 그래프(Knowledge Graph) 및 온톨로지(Ontology) 형태로 추상화하여 행동 트리의 실행 컨텍스트(Execution Context)에 결합하는 인지 로봇 공학(Cognitive Robotics)의 심화 아키텍처이다.

40.1 학술적 배경: 관계형 지식 표현과 온톨로지 모델링

행동 트리의 결정론적 분기를 의미론적 수준으로 끌어올리기 위해서는 로봇의 내부 상태(State)를 1차 논리(First-Order Logic) 또는 기술 논리(Description Logic) 기반의 지식 베이스(Knowledge Base, KB)로 정형화해야 한다.

지식 베이스 KB는 온톨로지(TBox, Terminological Box) \mathcal{O}와 사실의 집합(ABox, Assertional Box) \mathcal{F}로 구성된다.

KB = \mathcal{O} \cup \mathcal{F}

  • 온톨로지 \mathcal{O}: 도메인의 개념(Concept)과 그들 간의 계층적 포함 관계(Subsumption)를 정의한다. (예: Mug \sqsubseteq Container, Water \sqsubseteq Liquid)
  • 사실 집합 \mathcal{F}: 센서(Perception)를 통해 런타임에 접지(Grounding)된 특정 인스턴스와 관계를 명제화한다. (예: contains(Mug_1, Water_1), isOn(Mug_1, Table_2))

로봇이 특정 물체를 조작하기 전, 행동 트리의 조건 노드(Condition Node)는 KB에 의미론적 질의(Semantic Query) q를 던져 튜플의 존재 여부나 특정 속성을 만족하는 객체 집합을 논리적으로 반환받는다.

33.4 기호적 접지(Symbolic Grounding)와 동적 씬 그래프(Scene Graph) 동기화

행동 트리가 지식 그래프를 활용하기 위해서는 연속적인 센서 데이터(Vision, 3D Point Cloud)를 이산적인 기호(Symbol)로 변환하는 접지(Grounding) 파이프라인이 ROS2 노드로 백그라운드에서 구동되어야 한다.

  • 동적 씬 그래프(Dynamic Scene Graph): 퍼셉션 노드가 YOLO나 Mask R-CNN을 통해 객체를 인식하면, 이를 3D 공간 좌표와 결합하여 그래프 노드(Node)로 생성한다. 동시에 객체 간의 물리적 접촉(Contact)이나 포함(Containment) 관계를 엣지(Edge)로 모델링한다.
  • RDF 트리플(Triple) 변환: 추출된 씬 그래프는 (주어, 서술어, 목적어) 형태의 RDF(Resource Description Framework) 포맷으로 직렬화되어 인메모리(In-memory) 지식 베이스(예: KnowRob, Neo4j, TypeDB)에 실시간으로 갱신(Update)된다.

33.5 의미론적 질의 조건 노드(Semantic Query Condition Node) 설계

조건 노드 내부에 SPARQL이나 Cypher와 같은 그래프 데이터베이스 질의 엔진 인터페이스를 내장한다. 단순한 변수 비교(is_battery_low == true)를 넘어, “현재 관측된 객체 중 파지 가능(Graspable)하며 액체(Liquid)를 포함하고 있는 용기(Container)가 존재하는가?“와 같은 고차원적 논리 평가를 수행한다.

#include <behaviortree_cpp_v3/condition_node.h>
#include <rclcpp/rclcpp.hpp>
// 가상의 지식 베이스 클라이언트 라이브러리 가정 (예: ROS2 KnowRob 인터페이스)
#include <semantic_kb_client/sparql_client.hpp>

class SemanticQueryCondition : public BT::ConditionNode
{
public:
    SemanticQueryCondition(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node::SharedPtr ros_node)
        : BT::ConditionNode(name, config), ros_node_(ros_node)
    {
        kb_client_ = std::make_shared<SemanticKBClient>(ros_node_);
    }

    static BT::PortsList providedPorts()
    {
        return {
            BT::InputPort<std::string>("sparql_query", "SPARQL query string to evaluate against KB"),
            BT::OutputPort<std::string>("query_result_id", "ID of the entity that satisfies the query")
        };
    }

    BT::NodeStatus tick() override
    {
        std::string query_str;
        if (!getInput("sparql_query", query_str)) {
            throw BT::RuntimeError("Missing [sparql_query] input");
        }

        // 지식 베이스 서버에 SPARQL 질의 전송 및 추론(Reasoning) 요청
        auto response = kb_client_->sendQuery(query_str);

        // 질의 결과가 비어있지 않다면 조건 충족(SUCCESS)
        if (response.has_bindings()) {
            // 첫 번째로 매칭된 객체의 URI 또는 ID를 블랙보드에 기록하여 액션 노드로 전달
            std::string target_id = response.get_first_binding("target");
            setOutput("query_result_id", target_id);
            
            RCLCPP_INFO(ros_node_->get_logger(), "Semantic condition met. Target: %s", target_id.c_str());
            return BT::NodeStatus::SUCCESS;
        }

        // 지식 베이스 내에서 해당 조건을 만족하는 사실을 연역할 수 없음
        return BT::NodeStatus::FAILURE;
    }

private:
    rclcpp::Node::SharedPtr ros_node_;
    std::shared_ptr<SemanticKBClient> kb_client_;
};

33.6 XML 기반 의미론적 행동 트리 아키텍처 명세

위에서 정의된 SemanticQueryCondition을 활용하여, 하드코딩된 객체 ID(예: cup_01)가 아닌 관계형 논리에 의해 타겟을 동적으로 선정하고 파지(Grasping)하는 행동 트리 XML 명세이다.

<root main_tree_to_execute="SemanticGraspingTask">
    <BehaviorTree ID="SemanticGraspingTask">
        <Sequence name="Fetch_Beverage_Sequence">
            
            <SemanticQueryCondition ID="SemanticQueryCondition"
                sparql_query="
                    PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
                    PREFIX robo: &lt;http://ontology.robot.org/core#&gt;
                    SELECT ?target WHERE {
                        ?target rdfs:subClassOf robo:Beverage .
                        ?target robo:isOn ?surface .
                        ?surface a robo:Table .
                    } LIMIT 1
                "
                query_result_id="{target_object_id}" />
            
            <Action ID="GetEntityPose" entity_id="{target_object_id}" pose="{target_pose}"/>
            
            <Action ID="NavigateToPose" goal="{target_pose}"/>
            <Action ID="GraspEntity" entity_id="{target_object_id}"/>
            
        </Sequence>XML
    </BehaviorTree>
</root>

33.7 아키텍처의 학술적/공학적 의의

지식 그래프와 결합된 의미론적 행동 트리는 로봇 소프트웨어의 일반화(Generalization) 능력을 비약적으로 향상시킨다. 개발자가 “파란색 컵을 집어라“와 같이 특정 환경에 종속된(Over-fitted) 절차를 설계할 필요 없이, “목적에 부합하는 도구를 찾아라” 형태의 추상적 임무를 부여할 수 있다. 행동 트리의 논리 평가 엔진은 온톨로지 추론기(Reasoner)의 연역적 결론을 런타임에 이산적 분기(Branching)로 사상(Mapping)함으로써, 미지의 환경(Open-world)에서도 로봇이 유연하고 지능적으로 컨텍스트(Context)를 해석하여 행동할 수 있도록 수학적이고 구조적인 토대를 제공한다.