로봇을 하나의 프로그램으로 만들면 무슨 일이 생길까
이제 우리는 ROS2에서 노드를 만들고 실행하는 방법을 배웠다. 앞 장에서는 작은 노드 하나를 실행해 보고, 로그를 출력하고, ros2 run으로 프로그램을 시작했다. 처음에는 이것만으로도 꽤 그럴듯해 보인다. 그렇다면 이런 질문이 떠오를 수 있다.
'그냥 모든 기능을 하나의 Python 파일이나 C++ 프로그램에 넣으면 안 될까?'
작은 장난감 예제라면 가능하다. 키보드 입력을 읽고, 모터 속도를 계산하고, 센서 값을 출력하는 정도라면 하나의 프로그램으로도 움직일 수 있다. 하지만 자율주행 로봇은 금세 복잡해진다. LiDAR는 초당 수십 번 주변 거리를 측정하고, 바퀴 엔코더는 로봇이 얼마나 움직였는지 계속 알려 준다. 카메라는 이미지를 쏟아 내고, 위치 추정기는 로봇의 현재 위치를 계산한다. 경로 계획기는 목적지까지 가는 길을 찾고, 제어기는 실제 바퀴 속도를 정한다.
이 모든 일을 하나의 프로그램 안에 넣으면 처음에는 편해 보이지만, 곧 거대한 덩어리가 된다. 센서 코드에서 문제가 생겼는지, 위치 추정 코드가 틀렸는지, 경로 계획이 실패했는지, 모터 제어가 막혔는지 구분하기 어려워진다. 로봇이 벽 앞에서 멈췄을 때, 프로그램 전체를 열어 한 줄씩 추적해야 한다면 디버깅은 모험이 아니라 고문에 가까워진다.
ROS2의 노드는 이 문제를 해결하기 위한 기본 단위다. 노드는 로봇 소프트웨어를 이해 가능한 작은 부품으로 나누는 방법이다. 각각의 노드는 하나의 책임을 맡고, 다른 노드와 메시지를 주고받으며 전체 시스템을 이룬다. 마치 자동차가 엔진, 브레이크, 조향 장치, 센서, 계기판으로 나뉘어 있듯이, ROS2 로봇도 여러 노드가 협력해서 움직인다.

노드는 책임을 가진 작은 프로그램이다
ROS2에서 노드는 실행 중인 하나의 프로그램 단위라고 생각하면 쉽다. 더 정확히 말하면, 노드는 ROS2 통신망에 참여하는 독립적인 실행 구성 요소다. 노드는 토픽을 발행하거나 구독하고, 서비스를 제공하거나 요청하고, 액션 서버나 클라이언트가 될 수 있다. 하지만 처음부터 모든 통신 방식을 외우려 하기보다, 먼저 노드의 역할을 이렇게 이해하면 좋다.
노드는 '무엇을 책임지는가'로 구분한다.
예를 들어 이동 로봇에 2D LiDAR가 달려 있다고 하자. LiDAR 센서에서 원시 데이터를 읽어 /scan이라는 토픽으로 내보내는 노드가 있을 수 있다. 이 노드는 지도를 만들지도 않고, 목적지를 정하지도 않는다. 오직 LiDAR 장치와 통신해서 거리 데이터를 ROS2 메시지로 바꾸는 일에 집중한다. 이것이 좋은 노드 분리의 출발점이다.
반대로 좋지 않은 설계는 한 노드가 너무 많은 것을 아는 경우다. LiDAR 데이터를 읽고, 장애물을 판단하고, 경로를 만들고, 모터까지 직접 제어하는 노드는 처음에는 강력해 보인다. 하지만 센서를 바꾸거나 제어 알고리즘을 바꾸려는 순간 문제가 생긴다. 모든 코드가 서로 얽혀 있어 한 부분을 고치면 다른 부분이 깨질 수 있다.
좋은 노드는 자기 일을 명확히 안다. 그리고 다른 노드에게 필요한 정보는 정해진 통신 방식으로 전달한다. 이것이 ROS2가 로봇 소프트웨어를 다루는 가장 중요한 감각이다.
이동 로봇을 이루는 대표 노드들
자율주행 이동 로봇을 설계할 때 자주 등장하는 노드들은 대체로 역할에 따라 나눌 수 있다. 실제 로봇마다 이름과 구조는 조금씩 다르지만, 큰 흐름은 비슷하다.
센서 노드: LiDAR, 카메라, IMU, 엔코더 같은 하드웨어에서 데이터를 읽어 ROS2 메시지로 발행한다.
인식 노드: 센서 데이터를 해석해서 장애물, 차선, 사람, 표식 같은 의미 있는 정보를 뽑아낸다.
위치 추정 노드: 오도메트리, IMU, LiDAR, 지도 정보를 이용해 로봇이 어디에 있는지 계산한다.
계획 노드: 현재 위치와 목표 위치, 지도를 바탕으로 이동 경로를 만든다.
제어 노드: 계획된 경로나 속도 명령을 실제 바퀴, 모터, 조향 장치의 움직임으로 바꾼다.
이 다섯 가지는 앞으로 이 책 전체에서 반복해서 만날 기본 구조다. SLAM을 배울 때도 센서 노드와 위치 추정 노드가 등장하고, Nav2를 배울 때도 계획 노드와 제어 노드가 등장한다. 지금은 모든 내부 알고리즘을 이해하지 못해도 괜찮다. 중요한 것은 로봇이 한 번에 마법처럼 움직이는 것이 아니라, 여러 노드가 각자 맡은 일을 하며 데이터를 이어 준다는 점이다.
예를 들어 로봇이 복도 끝까지 이동한다고 해 보자. LiDAR 노드는 앞쪽 벽과 양옆 벽의 거리를 계속 발행한다. 오도메트리 노드는 바퀴 회전량을 이용해 로봇이 얼마나 이동했는지 추정한다. 위치 추정 노드는 이 정보를 바탕으로 지도 위 현재 위치를 계산한다. 계획 노드는 목표 지점까지의 경로를 만들고, 제어 노드는 그 경로를 따라가도록 /cmd_vel 속도 명령을 만든다. 마지막으로 모터 드라이버 노드는 /cmd_vel을 받아 실제 바퀴를 돌린다.
이 흐름을 글로 쓰면 길어 보이지만, ROS2에서는 각각이 독립된 노드로 실행된다. 그래서 특정 노드를 꺼 보거나, 토픽을 확인하거나, 다른 구현으로 교체하면서 시스템을 점진적으로 발전시킬 수 있다.
하나의 큰 프로그램보다 여러 노드가 좋은 이유
로봇 개발에서 노드를 나누는 이유는 단순히 멋진 구조를 만들기 위해서가 아니다. 실제로 개발 속도와 디버깅 능력을 크게 바꾼다. 로봇은 현실 세계에서 움직이기 때문에 예상하지 못한 문제가 자주 생긴다. 바퀴가 미끄러지고, 센서가 흔들리고, 케이블이 빠지고, 네트워크가 끊긴다. 이때 소프트웨어 구조가 엉켜 있으면 문제의 원인을 찾기 어렵다.
노드를 나누면 먼저 디버깅이 쉬워진다. 로봇이 움직이지 않는다고 해도 모든 코드를 의심할 필요가 없다. /cmd_vel 토픽이 나오고 있는지 확인하면 계획 또는 제어 노드가 속도 명령을 만들고 있는지 알 수 있다. /scan이 비어 있다면 LiDAR 노드나 센서 연결을 확인하면 된다. /odom이 이상하다면 엔코더 계산이나 오도메트리 노드를 살펴보면 된다. 문제의 범위를 좁히는 힘이 생기는 것이다.
두 번째 장점은 재사용이다. LiDAR 드라이버 노드는 SLAM에서도 쓰고, 장애물 회피에서도 쓰고, 단순 거리 경고 시스템에서도 쓸 수 있다. 오도메트리 노드도 마찬가지다. 한 번 잘 만든 노드는 다른 프로젝트에서도 다시 사용할 수 있다. ROS2 생태계에 이미 많은 패키지가 존재하는 이유도 여기에 있다. 모든 로봇 개발자가 매번 센서 드라이버부터 새로 만들 필요는 없다.
세 번째 장점은 교체 가능성이다. 처음에는 간단한 위치 추정 노드를 쓰다가 나중에 더 정교한 알고리즘으로 바꾸고 싶을 수 있다. 노드 사이의 입출력 토픽이 잘 정해져 있다면 내부 구현을 바꿔도 전체 시스템은 크게 흔들리지 않는다. 예를 들어 /odom을 발행하는 방식은 바뀌어도, 다른 노드들이 같은 메시지 형식으로 /odom을 구독한다면 연결 구조는 유지된다.
네 번째 장점은 병렬 개발이다. 팀 프로젝트라면 한 사람은 센서 노드를 만들고, 다른 사람은 제어 노드를 만들고, 또 다른 사람은 시각화와 Launch 파일을 준비할 수 있다. 각 노드의 입력과 출력만 약속하면 동시에 작업할 수 있다. 로봇 소프트웨어가 커질수록 이 차이는 매우 커진다.
노드를 나누는 기준
그렇다면 어디까지 나누어야 할까? 모든 함수를 노드로 만들면 좋을까? 그렇지는 않다. 너무 큰 노드는 문제가 되지만, 너무 작은 노드도 관리하기 어렵다. 노드가 많아지면 실행 파일, 토픽, 파라미터, Launch 설정이 늘어나고 전체 구조를 파악하기 어려워질 수 있다.
노드를 나눌 때는 몇 가지 질문을 던져 보면 좋다.
이 기능은 독립적으로 실행하거나 끌 수 있는가?
이 기능은 다른 로봇이나 프로젝트에서 재사용될 가능성이 있는가?
이 기능의 입력과 출력이 명확한가?
이 기능이 실패했을 때 다른 기능과 분리해서 진단할 수 있는가?
이 기능은 실행 주기나 성능 요구가 다른 기능과 크게 다른가?
예를 들어 LiDAR 드라이버는 독립 노드로 두는 것이 자연스럽다. 하드웨어와 직접 통신하고, 출력도 /scan처럼 명확하다. 모터 드라이버도 독립 노드로 두는 경우가 많다. 속도 명령을 받아 바퀴를 제어하는 책임이 분명하기 때문이다. 반면 아주 작은 계산 함수 하나를 별도 노드로 만들 필요는 없다. 같은 책임 안에 있는 내부 계산이라면 라이브러리나 클래스 함수로 두는 편이 낫다.
노드 분리의 핵심은 '프로그램 파일을 많이 만드는 것'이 아니라 '책임의 경계를 분명히 하는 것'이다. 이 경계가 잘 잡혀 있으면 시스템은 읽기 쉬워지고, 고치기 쉬워지고, 확장하기 쉬워진다.
실제 이동 로봇 노드 예제
이제 조금 더 구체적인 이동 로봇을 상상해 보자. 바퀴가 두 개 달린 차동 구동 로봇이 있고, 앞쪽에는 2D LiDAR가 달려 있다. 로봇은 실내에서 지도를 만들고, 나중에는 목표 지점까지 자율 이동해야 한다.
이 로봇에는 다음과 같은 노드들이 등장할 수 있다.
lidar_driver: LiDAR 장치에서 거리 데이터를 읽어/scan토픽으로 발행한다.wheel_odometry: 왼쪽과 오른쪽 바퀴의 회전량을 읽어/odom토픽으로 로봇 이동 추정값을 발행한다.base_controller:/cmd_vel토픽을 구독해 실제 모터 속도로 변환한다.slam_toolbox또는 SLAM 노드:/scan과/odom을 이용해 지도를 만들고 로봇 위치를 추정한다.planner: 지도와 목표 위치를 이용해 전역 경로를 만든다.controller: 경로를 따라가기 위한 속도 명령을 계산해/cmd_vel로 발행한다.
여기서 lidar_driver, wheel_odometry, base_controller는 로봇 하드웨어와 가까운 노드다. 실제 센서와 모터를 다루므로 로봇마다 구현이 달라질 수 있다. 반면 SLAM, 계획, 제어 노드는 비교적 알고리즘 중심이다. 하드웨어가 달라도 입력과 출력 형식이 맞으면 비슷한 구조로 사용할 수 있다.
데이터 흐름으로 보면 다음과 같다.
LiDAR 센서 → lidar_driver → /scan → SLAM 또는 장애물 인식
바퀴 엔코더 → wheel_odometry → /odom → 위치 추정
목표 지점 → planner → 경로 → controller → /cmd_vel → base_controller → 모터이 흐름에서 특히 중요한 토픽이 /cmd_vel이다. 이름 그대로 velocity command, 즉 속도 명령을 의미한다. 보통 이동 로봇에서 /cmd_vel은 앞으로 얼마나 빠르게 갈지, 얼마나 회전할지를 담는다. Nav2 같은 내비게이션 시스템도 최종적으로는 로봇을 움직이기 위해 속도 명령을 만들어야 한다.
하지만 /cmd_vel이 발행된다고 해서 로봇이 반드시 움직이는 것은 아니다. 그 명령을 구독하는 제어 노드가 있어야 하고, 제어 노드는 모터 드라이버와 제대로 연결되어 있어야 한다. 앞 장에서 명령어로 토픽과 노드를 확인했던 이유가 바로 여기에 있다. ROS2에서는 '명령을 보냈다'와 '로봇이 실제로 움직였다' 사이에 여러 노드가 있다. 그 사이를 하나씩 확인하는 습관이 좋은 로봇 개발자를 만든다.
센서 노드: 세상을 ROS2 메시지로 바꾸기
센서 노드는 로봇이 세상을 감지하는 입구다. LiDAR, 카메라, IMU, 초음파 센서, GPS, 바퀴 엔코더처럼 다양한 장치가 센서 노드의 대상이 된다. 센서 노드의 가장 중요한 역할은 하드웨어에서 얻은 데이터를 ROS2의 표준적인 형태로 바꾸는 것이다.
예를 들어 LiDAR는 각도별 거리값을 측정한다. 하지만 제조사마다 통신 방식이 다르고, 데이터 포맷도 다르다. 어떤 장치는 USB 시리얼로 값을 보내고, 어떤 장치는 Ethernet으로 패킷을 보낸다. 센서 노드는 이런 차이를 감추고, 다른 노드들이 이해할 수 있는 메시지로 데이터를 발행한다. 덕분에 SLAM 노드는 특정 LiDAR 모델의 내부 통신 방식을 몰라도 /scan 토픽만 구독하면 된다.
이것은 ROS2가 주는 큰 장점이다. 하드웨어의 복잡성을 센서 노드 안에 가두고, 나머지 시스템은 정리된 메시지를 사용한다. 물론 실제 개발에서는 센서 주기, 시간 정보, 프레임 이름, 노이즈 처리 같은 세부 사항이 중요하다. 하지만 첫 번째 원칙은 단순하다. 센서 노드는 로봇의 감각 기관이고, 그 감각을 ROS2 세계의 언어로 번역한다.
인식 노드: 데이터에서 의미를 찾기
센서 데이터는 그 자체로는 숫자의 묶음이다. LiDAR의 거리값, 카메라의 픽셀, IMU의 가속도와 각속도는 로봇이 바로 이해할 수 있는 '의미'가 아니다. 인식 노드는 이 데이터 속에서 로봇이 행동에 사용할 수 있는 정보를 찾아낸다.
예를 들어 LiDAR 데이터를 이용해 앞쪽 50cm 안에 장애물이 있는지 판단하는 노드를 만들 수 있다. 이 노드는 /scan을 구독하고, 가까운 거리값을 검사한 뒤, 장애물 여부를 다른 토픽으로 발행할 수 있다. 카메라 기반 로봇이라면 이미지에서 차선, 문, 사람, 표지판을 찾는 노드가 인식 노드가 된다.
인식 노드는 센서 노드와 계획 노드 사이의 중간 해석자 역할을 한다. 센서 노드가 '앞쪽 0도 방향 거리가 0.42m'라고 말한다면, 인식 노드는 '앞에 가까운 장애물이 있다'고 말한다. 계획 노드나 제어 노드는 이 의미 있는 정보를 이용해 멈추거나 돌아갈 수 있다.
모든 로봇에 복잡한 인식 노드가 필요한 것은 아니다. 단순한 실내 주행 로봇은 SLAM과 Costmap이 많은 역할을 대신할 수 있다. 그러나 로봇이 사람을 피하거나, 물체를 집거나, 특정 장소를 인식해야 한다면 인식 노드의 중요성은 커진다.
위치 추정 노드: 나는 지금 어디에 있는가
자율주행 로봇에게 가장 어려운 질문 중 하나는 '나는 어디에 있는가'이다. 사람은 복도에서 몇 걸음만 걸어도 주변 문과 벽을 보며 대략적인 위치를 알 수 있다. 로봇은 이 일을 숫자로 해야 한다.
위치 추정 노드는 로봇의 현재 위치와 방향을 계산한다. 가장 기본적인 정보는 오도메트리다. 오도메트리는 바퀴가 얼마나 회전했는지를 이용해 로봇이 얼마나 이동했는지 추정하는 방법이다. 하지만 바퀴는 미끄러질 수 있고, 바닥 상태에 따라 오차가 생긴다. 그래서 오도메트리만으로는 시간이 지날수록 위치가 조금씩 틀어진다.
나중에 배울 SLAM이나 AMCL 같은 알고리즘은 LiDAR 데이터와 지도를 이용해 이 오차를 줄인다. 여기서도 노드 구조는 중요하다. 바퀴 정보를 읽는 노드, 오도메트리를 발행하는 노드, 지도와 센서 데이터를 이용해 위치를 보정하는 노드가 서로 분리되어 있으면 각 단계를 확인할 수 있다.
로봇이 이상한 위치에 표시될 때는 질문을 나누어야 한다. 오도메트리가 잘 나오고 있는가? LiDAR 데이터가 올바른 좌표계에 붙어 있는가? 지도와 현재 스캔이 잘 맞는가? 위치 추정 노드가 어떤 입력을 받고 있는가? 노드가 분리되어 있으면 이런 질문에 하나씩 답할 수 있다.
계획 노드: 어디로 갈 것인가
계획 노드는 로봇의 현재 위치와 목표 위치를 이용해 이동 경로를 만든다. 사람에게 길 찾기를 맡기면 '이 복도를 따라가다가 오른쪽으로 돌고, 문 앞에서 멈춰'라고 말할 것이다. 로봇에게는 이 말을 좌표와 경로로 바꾸어 주어야 한다.
경로 계획은 크게 전역 계획과 지역 계획으로 나눌 수 있다. 전역 계획은 전체 지도에서 목표 지점까지의 큰 길을 찾는다. 지역 계획은 지금 로봇 주변의 장애물과 상황을 보며 실제로 어떤 속도로 움직일지 결정한다. Nav2에서는 이러한 역할이 여러 서버와 플러그인으로 나뉘어 있지만, 지금은 계획 노드가 '목표까지 가는 방법을 계산하는 역할'을 한다고 이해하면 충분하다.
계획 노드는 지도를 보고 벽을 통과하지 않는 경로를 찾아야 한다. 또한 장애물 근처를 너무 아슬아슬하게 지나가지 않도록 안전 여유를 고려해야 한다. 나중에 Costmap을 배우면 로봇 주변의 위험도를 격자 형태로 표현하는 방법을 자세히 보게 된다. 이 장에서는 계획 노드가 제어 노드에게 '따라갈 길'을 제공한다는 점에 집중하자.
제어 노드: 생각을 움직임으로 바꾸기
제어 노드는 로봇의 실제 움직임에 가까운 역할을 한다. 계획 노드가 경로를 만들었다면, 제어 노드는 그 경로를 따라가기 위해 지금 얼마만큼 전진하고 회전해야 하는지 계산한다. 이동 로봇에서는 이 결과가 보통 /cmd_vel 토픽으로 표현된다.
예를 들어 로봇이 경로보다 왼쪽으로 조금 벗어났다면 제어 노드는 오른쪽으로 살짝 회전하는 명령을 만들 수 있다. 목표에 가까워지면 속도를 줄이고, 장애물이 가까우면 멈추거나 우회하도록 속도를 조정할 수 있다. 제어 노드는 로봇의 움직임을 부드럽고 안정적으로 만드는 데 중요하다.
그다음에는 더 낮은 수준의 제어 노드가 있을 수 있다. /cmd_vel은 로봇에게 '앞으로 0.2m/s, 왼쪽으로 0.4rad/s 회전'처럼 말한다. 하지만 실제 모터는 전압, PWM, 회전수 같은 값으로 제어된다. base_controller나 모터 드라이버 노드는 이 속도 명령을 바퀴별 목표 속도로 바꾸고, 하드웨어에 전달한다.
이처럼 제어에도 층이 있다. 위쪽 제어 노드는 로봇의 경로 추종을 생각하고, 아래쪽 제어 노드는 바퀴와 모터를 직접 다룬다. 이 둘을 분리하면 시뮬레이션과 실제 로봇 사이를 오가기도 쉬워진다. 시뮬레이션에서는 가상 모터가 /cmd_vel을 받고, 실제 로봇에서는 하드웨어 드라이버가 같은 토픽을 받을 수 있다.
rqt_graph로 노드 그래프 확인하기
노드를 나누었다면, 이제 정말 의도한 대로 연결되었는지 확인해야 한다. ROS2에서는 rqt_graph를 사용해 노드와 토픽의 연결 관계를 그림으로 볼 수 있다. 터미널에서 다음 명령을 실행하면 된다.
rqt_graph화면에는 실행 중인 노드와 토픽이 그래프 형태로 나타난다. 어떤 노드가 어떤 토픽을 발행하고, 어떤 노드가 그 토픽을 구독하는지 화살표로 볼 수 있다. 처음 보면 선이 복잡해 보일 수 있지만, 조금만 익숙해지면 로봇 시스템의 혈관 지도를 보는 느낌이 든다.
예를 들어 다음과 같은 구조를 기대한다고 해 보자.
/lidar_driver → /scan → /slam_node
/wheel_odometry → /odom → /slam_node
/nav_controller → /cmd_vel → /base_controllerrqt_graph에서 이 흐름이 보이면 최소한 노드 연결은 의도와 맞는다고 볼 수 있다. 반대로 /cmd_vel을 발행하는 노드는 있는데 구독하는 노드가 없다면, 로봇이 움직이지 않는 이유가 분명해진다. 속도 명령은 만들어졌지만 그것을 받아 모터로 바꾸는 노드가 없는 것이다.
또 다른 흔한 문제는 토픽 이름이 서로 다른 경우다. 한 노드는 /cmd_vel로 발행하는데, 다른 노드는 /robot/cmd_vel을 구독하고 있을 수 있다. 이 경우 양쪽 노드는 모두 정상 실행 중이지만 서로 만나지 못한다. 사람으로 치면 같은 방에 있으면서 서로 다른 채팅방에 글을 쓰는 셈이다.
rqt_graph는 이런 문제를 빠르게 보여 준다. 터미널 명령어로 하나씩 확인하는 것도 중요하지만, 전체 흐름을 그림으로 보는 것은 설계 검증에 큰 도움이 된다. 로봇이 복잡해질수록 그래프를 보는 습관은 필수에 가까워진다.
노드 이름은 생각보다 중요하다
ROS2에서 노드 이름은 단순한 장식이 아니다. 노드를 구분하고, 로그를 확인하고, 파라미터를 적용하고, 그래프에서 구조를 이해하는 기준이 된다. 이름이 대충 붙어 있으면 시스템이 커졌을 때 혼란이 생긴다.
예를 들어 모든 테스트 노드 이름을 node라고 붙였다고 해 보자. 하나만 실행할 때는 문제가 없다. 하지만 여러 개가 동시에 실행되면 어떤 노드가 어떤 역할을 하는지 알기 어렵다. 심지어 같은 이름의 노드가 여러 개 실행되면 도구에서 구분하기 어렵거나 경고가 발생할 수 있다. 특히 파라미터나 라이프사이클 노드처럼 이름을 기준으로 제어하는 경우에는 더 조심해야 한다.
좋은 노드 이름은 역할을 드러낸다. lidar_driver, odom_publisher, cmd_vel_mux, base_controller, map_server, amcl, planner_server처럼 이름만 봐도 대략 무엇을 하는지 알 수 있어야 한다. 이름이 길어지는 것이 항상 나쁜 것은 아니다. 모호한 짧은 이름보다 분명한 이름이 낫다.
프로젝트가 커지면 노드 이름과 토픽 이름의 규칙을 정해 두는 것이 좋다. 예를 들어 센서 드라이버는 *_driver, 데이터를 가공해 발행하는 노드는 *_publisher, 하드웨어를 제어하는 노드는 *_controller처럼 약속할 수 있다. 꼭 이 규칙을 따라야 하는 것은 아니지만, 팀 안에서 일관성이 있어야 한다.
네임스페이스: 로봇이 한 대에서 여러 대로 늘어날 때
처음에는 로봇 한 대만 생각하면 된다. 하지만 ROS2는 여러 로봇을 함께 다루는 상황도 고려할 수 있다. 이때 중요한 개념이 네임스페이스다. 네임스페이스는 노드와 토픽 이름 앞에 붙는 이름 공간이다. 쉽게 말해 같은 이름을 가진 노드와 토픽을 서로 다른 영역에 넣어 충돌을 피하는 방법이다.
로봇 한 대에서는 /scan, /odom, /cmd_vel이라는 이름이 자연스럽다. 그런데 로봇이 두 대라면 어떻게 될까? 두 로봇이 모두 /scan을 발행하면, 구독하는 노드는 어느 로봇의 LiDAR 데이터인지 구분하기 어렵다. 두 로봇이 모두 /cmd_vel을 구독한다면, 한 명령이 두 로봇을 동시에 움직일 수도 있다. 상상만 해도 아찔하다. 한 로봇에게 '앞으로 가'라고 했는데 옆에 있던 다른 로봇도 같이 출발한다면, 실험실은 금세 작은 교통사고 현장이 된다.
네임스페이스를 사용하면 다음처럼 구분할 수 있다.
/robot1/scan
/robot1/odom
/robot1/cmd_vel
/robot2/scan
/robot2/odom
/robot2/cmd_vel이제 robot1의 내비게이션 노드는 /robot1/scan과 /robot1/odom을 사용하고, /robot1/cmd_vel로 명령을 보낸다. robot2도 자기 네임스페이스 안에서 같은 구조를 가질 수 있다. 내부 구조는 같지만 이름 공간이 다르기 때문에 충돌하지 않는다.
네임스페이스 설계는 멀티로봇뿐 아니라 복잡한 단일 로봇에서도 도움이 된다. 예를 들어 앞쪽 카메라와 뒤쪽 카메라가 모두 이미지 토픽을 발행한다면 /front_camera/image와 /rear_camera/image처럼 나눌 수 있다. 왼쪽 LiDAR와 오른쪽 LiDAR가 있다면 /left_lidar/scan, /right_lidar/scan처럼 표현할 수 있다.
중요한 것은 이름이 데이터의 소속을 드러내야 한다는 점이다. 토픽 이름만 보고도 '어느 로봇의 어떤 센서 데이터인지', '어느 제어기에 들어가는 명령인지'를 알 수 있어야 한다. 이름은 설계 문서의 일부다.
노드 분리에서 자주 하는 실수
노드 설계는 경험이 쌓일수록 좋아진다. 처음부터 완벽하게 나누기는 어렵다. 그래도 몇 가지 흔한 실수를 알고 있으면 시행착오를 줄일 수 있다.
한 노드가 너무 많은 책임을 가진다.
토픽 이름을 즉흥적으로 정해 나중에 연결이 꼬인다.
노드 이름이 모호해서 로그와 그래프를 보고도 역할을 알 수 없다.
센서 드라이버와 알고리즘을 강하게 묶어 센서 교체가 어렵다.
네임스페이스를 고려하지 않아 로봇이 늘어날 때 구조를 다시 뜯어고친다.
이 중 가장 흔한 실수는 '일단 돌아가게 만들자'는 마음으로 모든 것을 한 노드에 넣는 것이다. 물론 초기 실험에서는 빠르게 움직이는 것이 중요하다. 하지만 실험 코드가 그대로 프로젝트의 중심이 되면 나중에 큰 비용을 치르게 된다. 어느 순간부터는 기능을 분리하고, 토픽 이름을 정리하고, Launch 파일로 실행 구조를 만드는 시간이 필요하다.
또 하나의 실수는 노드 분리를 알고리즘 분리와 혼동하는 것이다. 모든 알고리즘을 반드시 별도 노드로 만들 필요는 없다. 같은 책임 안에서만 쓰이는 계산은 하나의 노드 내부 함수로 있어도 좋다. 반대로 다른 기능과 독립적으로 실행되고, 입력과 출력이 분명하며, 교체 가능성이 있다면 별도 노드로 만드는 것이 좋다.
작은 로봇 시스템을 설계해 보기
이제 머릿속으로 작은 로봇 시스템을 설계해 보자. 로봇은 LiDAR와 바퀴 엔코더를 가지고 있고, 사용자는 키보드나 내비게이션 시스템으로 속도 명령을 보낼 수 있다. 로봇은 아직 완전한 자율주행을 하지 않아도 된다. 중요한 것은 노드를 어떻게 나눌지 생각하는 것이다.
가장 단순한 구조는 다음과 같다.
/lidar_driver ── publishes ── /scan
/odom_publisher ── publishes ── /odom
/teleop_keyboard ── publishes ── /cmd_vel
/base_controller ── subscribes ── /cmd_vel이 구조에서 사용자는 키보드 노드로 로봇을 조종한다. LiDAR 노드는 주변 거리 데이터를 계속 발행하고, 오도메트리 노드는 로봇의 이동 추정을 발행한다. 아직 /scan과 /odom을 사용하는 고급 노드는 없을 수 있다. 그래도 괜찮다. 나중에 SLAM 노드를 추가하면 /scan과 /odom을 구독해서 지도를 만들 수 있다. Nav2를 추가하면 지도와 위치를 바탕으로 /cmd_vel을 발행할 수 있다.
즉, 좋은 구조는 미래의 확장을 방해하지 않는다. 지금은 키보드로 움직이지만, 나중에는 자율주행 노드가 같은 /cmd_vel을 발행할 수 있다. 지금은 센서 데이터를 보기만 하지만, 나중에는 SLAM과 장애물 회피가 같은 /scan을 사용할 수 있다. 노드와 토픽을 잘 나누어 두면 프로젝트는 자연스럽게 성장한다.
노드 그래프를 설계도처럼 읽기
ROS2 시스템을 볼 때는 단순히 노드 목록만 보는 것보다 데이터 흐름을 함께 보는 것이 중요하다. ros2 node list는 현재 어떤 노드가 떠 있는지 알려 준다. ros2 topic list는 어떤 토픽이 있는지 알려 준다. 하지만 전체 구조를 이해하려면 '누가 무엇을 만들고, 누가 그것을 사용하는가'를 봐야 한다.
노드 그래프를 읽을 때는 왼쪽에서 오른쪽으로 데이터가 흘러간다고 상상해 보자. 센서 데이터는 보통 하드웨어에서 시작해 인식과 위치 추정으로 흐른다. 목표 지점과 지도 정보는 계획으로 들어가고, 계획 결과는 제어를 거쳐 속도 명령이 된다. 속도 명령은 마지막에 모터 제어기로 들어간다.
이 흐름이 자연스럽다면 시스템은 이해하기 쉽다. 반대로 제어 노드가 센서 드라이버 내부 상태를 직접 읽는다거나, 계획 노드가 모터 포트를 직접 제어한다면 경계가 무너진 것이다. 물론 아주 특수한 상황에서는 예외가 있을 수 있지만, 일반적인 이동 로봇에서는 계층을 분리하는 편이 안전하다.
노드 그래프는 단순한 그림이 아니라 실행 중인 시스템의 설계도다. 설계도와 실제 연결이 다르면 로봇은 이상하게 움직인다. 그래서 우리는 코드를 작성한 뒤 반드시 실행 중인 노드와 토픽을 확인해야 한다. 로봇 개발에서 '돌아간다'는 말은 프로그램이 실행된다는 뜻만이 아니다. 올바른 데이터가 올바른 노드로 흐른다는 뜻이다.
다음 단계로 가기 전에
이번 장의 핵심은 노드 분리가 자율주행 로봇을 이해 가능한 작은 부품들의 조합으로 만드는 첫 단계라는 것이다. ROS2를 잘 사용한다는 것은 단순히 명령어를 많이 아는 것이 아니다. 로봇의 기능을 어떤 노드로 나눌지, 각 노드가 어떤 데이터를 주고받을지, 이름과 네임스페이스를 어떻게 설계할지 생각할 수 있어야 한다.
이제 우리는 로봇 시스템을 다음과 같이 바라볼 수 있다.
센서 노드는 세상의 물리 데이터를 ROS2 메시지로 바꾼다.
인식 노드는 센서 데이터에서 의미 있는 정보를 찾는다.
위치 추정 노드는 로봇이 어디에 있는지 계산한다.
계획 노드는 목표까지 가는 길을 만든다.
제어 노드는 계획을 실제 움직임으로 바꾼다.
앞으로 배울 토픽, 서비스, 액션, 파라미터, Launch는 모두 이 노드들을 연결하고 운영하기 위한 도구다. 다음 장에서는 그중 가장 기본이 되는 토픽과 메시지를 본격적으로 다룬다. 노드가 로봇의 작은 기관이라면, 토픽은 그 기관 사이를 흐르는 신경 신호에 가깝다. 이제 로봇 안에서 데이터가 어떻게 흐르는지 들여다볼 차례다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.