# 노드(Node) 생성과 관리

#### 노드의 정의

ROS2에서 \*\*노드(Node)\*\*는 시스템에서 실행되는 기본 단위로, 노드는 여러 프로세스에 걸쳐 분산될 수 있으며, 각 노드는 서로 통신을 통해 데이터를 주고받는다. 노드는 일반적으로 특정 기능을 수행하는 작은 단위의 프로그램이며, 노드 간의 통신을 통해 전체 시스템의 작업을 분배하고 협력하여 큰 작업을 수행한다.

#### 노드의 구조

ROS2 노드는 **rclcpp**(C++) 또는 **rclpy**(Python) 라이브러리를 사용하여 구현된다. 각각의 노드는 다음과 같은 구조적 요소로 구성된다.

* **이름(Name)**: 각 노드는 고유한 이름을 갖는다. 노드의 이름은 네트워크 상에서 노드를 구분하는 데 사용되며, 시스템 내에서 노드 간의 충돌을 피하기 위해 고유해야 한다.
* **네임스페이스(Namespace)**: 여러 노드를 그룹화하여 동일한 이름의 노드가 다른 네임스페이스 내에 있을 수 있다. 네임스페이스를 통해 논리적 그룹을 만들어 관리하기 쉬워진다.

**노드 이름과 네임스페이스 예시**

예를 들어, 두 개의 로봇이 각각 동일한 노드를 가지고 있지만, 네임스페이스로 구분된다면 다음과 같이 관리할 수 있다.

* `/robot1/camera_node`
* `/robot2/camera_node`

#### 노드 생성

ROS2에서 노드를 생성하는 방법은 프로그래밍 언어에 따라 다르며, 다음과 같은 방법으로 노드를 만들 수 있다.

**C++에서의 노드 생성**

\*\*C++\*\*에서 노드를 생성할 때는 **rclcpp** 라이브러리를 사용한다. 간단한 노드를 생성하는 코드는 아래와 같다.

```cpp
#include "rclcpp/rclcpp.hpp"

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv); // ROS2 초기화
    auto node = std::make_shared<rclcpp::Node>("my_node"); // 노드 생성
    rclcpp::spin(node); // 노드 실행
    rclcpp::shutdown(); // ROS2 종료
    return 0;
}
```

**Python에서의 노드 생성**

**Python**에서 노드를 생성할 때는 **rclpy** 라이브러리를 사용한다. 아래는 Python으로 노드를 생성하는 예제이다.

```python
import rclpy
from rclpy.node import Node

def main(args=None):
    rclpy.init(args=args) # ROS2 초기화
    node = Node("my_node") # 노드 생성
    rclpy.spin(node) # 노드 실행
    node.destroy_node() # 노드 종료
    rclpy.shutdown() # ROS2 종료

if __name__ == '__main__':
    main()
```

#### 노드의 관리

노드 관리에는 다음과 같은 작업이 포함된다.

* **노드 실행**: 노드를 생성하고 실행하는 것은 ROS2 시스템에서 기본적으로 수행하는 작업이다. C++에서는 `rclcpp::spin()`, Python에서는 `rclpy.spin()`을 사용하여 노드를 실행한다.
* **노드 종료**: 노드를 종료할 때는 `rclcpp::shutdown()` 또는 `rclpy.shutdown()` 함수를 호출하여 ROS2 시스템을 정상적으로 종료한다. 이는 시스템 내에서 사용하던 자원을 해제하고, 더 이상 통신하지 않음을 알린다.

#### 노드의 상태 관리

ROS2 노드는 상태를 관리할 수 있는 생명주기(Lifecycle) 노드로 확장할 수 있다. 그러나 기본적인 노드는 상태 관리가 필요하지 않으며, 다음과 같은 간단한 상태 전환이 이루어진다.

1. **생성 상태**: 노드가 처음 생성되고, 필요한 초기화 작업이 완료되는 상태이다.
2. **실행 상태**: 노드가 동작하면서 통신을 주고받는 상태이다.
3. **종료 상태**: 노드가 종료되고, 메모리 자원과 통신이 해제되는 상태이다.

다음으로는 **퍼블리셔**와 **서브스크라이버**를 통해 통신을 설정하는 과정이 이어진다.

#### 퍼블리셔와 서브스크라이버 설정

ROS2 노드 간의 통신은 기본적으로 \*\*퍼블리셔(Publisher)\*\*와 **서브스크라이버(Subscriber)** 개념을 통해 이루어진다. 퍼블리셔는 데이터를 송신하고, 서브스크라이버는 해당 데이터를 수신하는 방식으로 동작한다. 퍼블리셔와 서브스크라이버는 \*\*토픽(Topic)\*\*이라는 채널을 통해 데이터를 교환한다.

**퍼블리셔(Publisher) 생성**

퍼블리셔는 ROS2 노드가 다른 노드에게 정보를 전송할 수 있도록 설정된 객체이다. 퍼블리셔는 특정한 **토픽**을 통해 데이터를 송신하며, 이 토픽에 등록된 서브스크라이버들은 그 데이터를 수신한다.

**C++에서 퍼블리셔 생성**

C++에서 퍼블리셔를 생성하는 방법은 다음과 같다.

```cpp
auto publisher = node->create_publisher<std_msgs::msg::String>("topic_name", 10);
```

위 코드에서 퍼블리셔는 `std_msgs::msg::String` 타입의 메시지를 발행하며, \*\*"topic\_name"\*\*이라는 토픽으로 데이터를 전송한다. 마지막 인자는 \*\*큐 사이즈(queue size)\*\*로, 송신 대기열의 크기를 지정한다.

**Python에서 퍼블리셔 생성**

Python에서는 다음과 같이 퍼블리셔를 생성한다.

```python
publisher = node.create_publisher(String, "topic_name", 10)
```

여기서 `String`은 발행할 메시지 타입이며, `"topic_name"`은 퍼블리싱할 토픽 이름이다.

**서브스크라이버(Subscriber) 생성**

서브스크라이버는 특정 토픽에서 데이터를 수신하기 위해 노드에 설정된 객체이다. 서브스크라이버는 퍼블리셔가 송신하는 데이터를 받아서 처리할 수 있다.

**C++에서 서브스크라이버 생성**

C++에서 서브스크라이버를 생성하는 방법은 다음과 같다.

```cpp
auto subscription = node->create_subscription<std_msgs::msg::String>(
    "topic_name", 10, [](const std_msgs::msg::String::SharedPtr msg) {
        RCLCPP_INFO(node->get_logger(), "I heard: '%s'", msg->data.c_str());
    });
```

위 코드는 \*\*"topic\_name"\*\*이라는 토픽에서 데이터를 수신하는 서브스크라이버를 생성하며, 수신된 메시지를 처리하는 콜백 함수를 등록한다.

**Python에서 서브스크라이버 생성**

Python에서는 서브스크라이버를 다음과 같이 생성할 수 있다.

```python
subscription = node.create_subscription(
    String,
    "topic_name",
    lambda msg: node.get_logger().info('I heard: "%s"' % msg.data),
    10)
```

이 코드에서는 `"topic_name"`이라는 토픽에서 데이터를 받아 처리하며, 콜백 함수는 수신된 메시지를 출력하는 방식으로 동작한다.

#### 퍼블리셔와 서브스크라이버의 통신 흐름

퍼블리셔와 서브스크라이버는 \*\*토픽(Topic)\*\*을 통해 통신하며, 토픽을 기준으로 데이터를 주고받는다. 이를 시각적으로 설명하면 다음과 같다.

{% @mermaid/diagram content="graph LR
A\[퍼블리셔] --> |데이터 전송| B\[토픽]
B --> |데이터 수신| C\[서브스크라이버1]
B --> |데이터 수신| D\[서브스크라이버2]" %}

위 다이어그램은 퍼블리셔가 데이터를 송신하고, 여러 서브스크라이버가 동일한 토픽에서 데이터를 수신하는 과정을 보여준다.

#### 통신 설정과 QoS 정책

퍼블리셔와 서브스크라이버는 **QoS(Quality of Service)** 정책을 통해 통신 품질을 관리할 수 있다. QoS는 네트워크 상태와 응용 프로그램의 요구 사항에 따라 데이터를 어떻게 처리할지를 정의한다.

**QoS 설정 예시 (C++)**

```cpp
rclcpp::QoS qos(rclcpp::KeepLast(10)); // 최신 10개의 메시지만 유지
auto publisher = node->create_publisher<std_msgs::msg::String>("topic_name", qos);
```

**QoS 설정 예시 (Python)**

```python
qos = QoSProfile(depth=10) # 최신 10개의 메시지만 유지
publisher = node.create_publisher(String, "topic_name", qos)
```

**QoS 프로파일**은 다양한 통신 시나리오에 맞추어 네트워크 성능을 최적화할 수 있다.

#### 노드의 실행 및 주기적 작업 관리

노드는 다양한 방식으로 실행될 수 있으며, 특히 주기적인 작업(예: 주기적으로 센서 데이터를 수집하거나, 주기적으로 데이터를 퍼블리싱하는 작업)을 관리하기 위해 ROS2의 **타이머(Timer)** 기능을 활용할 수 있다.

**타이머(Timer) 생성**

**타이머**는 노드가 일정한 시간 간격으로 작업을 수행할 수 있게 한다. 타이머는 주어진 주기마다 콜백 함수를 실행하여 노드가 특정 작업을 반복적으로 수행할 수 있게 해준다.

**C++에서 타이머 생성**

C++에서 타이머를 생성하는 방법은 다음과 같다.

```cpp
auto timer = node->create_wall_timer(
    std::chrono::milliseconds(500),
    []() {
        RCLCPP_INFO(node->get_logger(), "Timer callback executed.");
    });
```

위 코드는 500 밀리초마다 콜백 함수가 실행되는 타이머를 설정하는 예시이다. \*\*`create_wall_timer()`\*\*는 시스템의 벽 시계 시간을 기준으로 타이머를 실행한다.

**Python에서 타이머 생성**

Python에서는 다음과 같이 타이머를 생성할 수 있다.

```python
timer = node.create_timer(0.5, lambda: node.get_logger().info('Timer callback executed.'))
```

여기서 \*\*`0.5`\*\*는 0.5초마다 타이머가 콜백 함수를 실행한다는 의미이다.

#### 노드 간 통신 흐름 예시

주기적으로 데이터를 퍼블리시하는 노드와 이를 구독하는 노드 간의 상호작용은 아래와 같이 정리될 수 있다.

{% @mermaid/diagram content="graph TD
P\[퍼블리셔 노드] --> |주기적 퍼블리싱| T\[토픽]
S1\[서브스크라이버 노드1] --> |데이터 수신| T
S2\[서브스크라이버 노드2] --> |데이터 수신| T" %}

이 다이어그램은 퍼블리셔 노드가 정기적으로 데이터를 퍼블리시하고, 여러 서브스크라이버 노드가 이를 수신하는 통신 흐름을 나타낸다.

#### 노드의 상태 모니터링

ROS2 시스템 내에서 노드의 상태를 모니터링하는 것은 매우 중요하다. 이를 위해 ROS2는 다양한 도구와 명령어를 제공하며, 노드의 현재 상태, 토픽, 서비스 등의 상태를 실시간으로 확인할 수 있다.

**노드 상태 확인 명령어**

* **노드 목록 확인**: 현재 실행 중인 모든 노드를 확인하는 명령어는 다음과 같다.

  ```bash
  ros2 node list
  ```
* **노드 정보 확인**: 특정 노드에 대한 정보를 확인하는 명령어는 다음과 같다.

  ```bash
  ros2 node info <node_name>
  ```

#### 멀티 노드 시스템에서의 관리

ROS2 시스템은 분산된 노드들로 구성될 수 있으며, 멀티 노드 시스템에서의 관리가 필요하다. 각 노드는 독립적인 프로세스로 실행되며, 이를 효율적으로 관리하기 위한 다양한 기능이 제공된다.

**멀티 노드 관리 예시**

멀티 노드 시스템에서 중요한 요소는 노드 간의 네임스페이스를 잘 관리하는 것과, 노드가 적절한 리소스를 사용할 수 있도록 통신의 우선순위나 QoS를 잘 설정하는 것이다.

예를 들어, 하나의 로봇 시스템에서 두 개의 센서 노드가 독립적으로 데이터 수집을 수행하고, 이를 하나의 중앙 노드가 처리한다고 가정해 봅시다.

{% @mermaid/diagram content="graph TD
SensorNode1\[센서 노드1] --> |데이터 전송| CentralNode\[중앙 노드]
SensorNode2\[센서 노드2] --> |데이터 전송| CentralNode" %}

#### 예외 처리와 에러 핸들링

ROS2 노드를 생성하고 관리하는 과정에서 발생할 수 있는 예외와 오류를 처리하는 것은 안정적인 시스템 운영을 위해 매우 중요하다. 예를 들어, 노드가 특정 리소스에 접근하지 못할 경우나, 네트워크 상의 통신 문제가 발생할 때 적절한 대처가 필요하다.

**C++에서의 예외 처리**

C++에서는 `try-catch` 구문을 사용하여 예외를 처리할 수 있다.

```cpp
try {
    // 노드 실행 코드
} catch (const std::exception &e) {
    RCLCPP_ERROR(node->get_logger(), "Exception: %s", e.what());
}
```

**Python에서의 예외 처리**

Python에서도 `try-except` 구문을 통해 예외 처리를 할 수 있다.

```python
try:
    # 노드 실행 코드
except Exception as e:
    node.get_logger().error(f"Exception: {str(e)}")
```
