노드 역할과 메시지 계약
150분 안팎
학습 목표
센서·상태 표시·구동 노드의 입력과 출력을 구성도로 제출합니다.
개념
읽기·전달·표시의 책임을 나눕니다
앞 모듈에서는 C HAL이 센서 거리와 모터 요청을 기록했습니다. 이제 센서 로그를 읽는 코드에 화면 문구를 붙이고, 화면 코드에서 모터를 직접 바꾸기 시작하면 어떤 입력 때문에 출력이 달라졌는지 추적하기 어렵습니다. 이번 목표는 코드를 여러 파일로 쪼개는 데 있지 않습니다. 각 역할의 입력과 출력, 오류 때 할 일을 정하고 구성도로 제출하는 데 있습니다. 신입 코드 리뷰에서도 함수 개수보다 책임이 드러나는 연결선을 먼저 봅니다.
이 모듈의 실습은 Python 3 표준 라이브러리와 bash, 미션의 C11 컴파일러 gcc를 사용합니다. ROS 2 설치와 실물 센서는 필요하지 않습니다. 로컬 ZIP을 푼 폴더에서 bash check.sh를 실행합니다. starter는 핵심 TODO 때문에 일부 기능 검사가 실패하고 solution은 같은 테스트를 통과합니다. 브라우저 실습은 표준 입력과 출력을 사용합니다. 모든 시각은 벽시계 대기 없이 진행하는 가상 초이며 실제 Linux 노드와 DDS 연결 검증은 후속 환경에서 수행합니다.
노드는 역할이고 프로세스는 배치입니다
ROS 2 노드는 그래프에 참여하는 계산 단위입니다. 한 가지 논리적 일을 맡도록 경계를 잡되 노드 하나가 프로세스 하나여야 한다고 제한하지 않습니다. 같은 프로세스에도 여러 노드를 배치할 수 있고 다른 프로세스나 컴퓨터에서도 통신할 수 있습니다. 프로세스를 나누면 장애 격리 방법이 달라지지만, 토픽을 썼다는 사실만으로 예외와 자원 고갈이 자동 격리되지는 않습니다. 이번 Python 객체에는 실제 프로세스 격리가 없습니다.
구성도에는 sensor_node, status_node, drive_node 세 역할을 놓습니다. sensor_node는 HAL 행을 상태 메시지로 바꾸고 발행합니다. status_node는 수신 메시지를 화면용 문자열로 바꾸며 모터 요청을 쓰지 않습니다. drive_node는 별도 구동 요청을 HAL의 set_wheels로 전달합니다. 이번 미션에서 새로 구현하는 연결은 앞의 두 역할입니다. 구동 역할은 다음 제어 기능이 붙을 경계로 남겨 두며 화면 수신 성공을 이동 허가로 사용하지 않습니다.
입력마다 생산자와 의미를 적습니다
센서 역할의 입력은 C 시뮬레이터가 만든 JSONL입니다. 한 행에는 seq, stamp_s, frame_id, range_m, sample_valid, status와 두 바퀴 요청이 있습니다. seq는 측정 로그 번호이고 stamp_s는 그 측정을 시도한 시각입니다. range_m는 sensor 기준 전방 거리이며 map 원점까지의 거리가 아닙니다. 상태를 화면에 옮겨도 이 의미는 그대로 유지합니다. 전달 계약에 필드 이름과 단위를 함께 적으면 다른 노드가 같은 숫자를 다르게 해석하는 일을 줄입니다.
앞 모듈의 정상 행은 sample_valid=true와 status=0이고 센서 결함 행은 false와 status=2입니다. 실패 행의 range_m는 마지막 정상값이므로 숫자가 범위 안에 있어도 현재 관측이 아닙니다. 화면은 그 값 대신 INVALID를 표시하고 마지막 측정 시각과 오류 코드는 남깁니다. 로그 원본에서 거리 숫자를 삭제하지는 않습니다. 진단 자료 보존과 유효 관측으로 사용하기는 서로 다른 책임입니다.
토픽 화살표에 계약을 붙입니다
센서에서 상태 화면으로 가는 화살표에는 /robot/range_status, bootcamp/RangeStatusV1, 발행 간격 0.05초를 적습니다. 타입 문자열은 이 PC 모델의 식별 라벨이며 실제 설치된 ROS 인터페이스가 아닙니다. 실제 ROS로 옮길 때에는 메시지 패키지를 빌드하고 발행자와 구독자가 같은 인터페이스를 사용하도록 해야 합니다. 역할 설계의 성공을 실제 패키지 빌드 완료로 표현하지 않습니다.
토픽은 반복 상태를 여러 소비자가 관심에 따라 받도록 하는 인터페이스입니다. 발행자는 구독 객체의 이름을 하드코딩할 필요가 없습니다. 화면과 기록기가 같은 상태를 구독할 수 있습니다. 다만 이번 버스는 한 구독자만 지원하므로 다중 구독을 시험한 결과는 없습니다. 향후 확장에서는 소비자별 큐를 분리해야 합니다. 하나의 큐에서 먼저 꺼낸 소비자가 메시지를 가져가는 방식은 각 구독자에게 상태를 전달하는 모델과 다릅니다.
구성도와 필드표를 함께 검토합니다
화살표만 그리면 누가 어떤 실패를 처리하는지 빠지기 쉽습니다. 구성도 옆 표에 역할, 입력, 출력, 갱신 시점, 실패 동작을 씁니다. sensor_node의 행은 기본 메시지 검증 실패를 보고하고 해당 입력을 사용하지 않는다는 내용입니다. status_node의 행은 최초 수신 전 NO_DATA, 정상 수신 뒤 거리 표시, 결함 수신 뒤 INVALID 표시입니다. drive_node의 행에는 오류 잠금과 출력 0을 유지하는 기존 HAL 계약을 기록합니다.
데이터의 방향도 확인합니다. status_node가 C 구조체 주소를 직접 받으면 표시 역할이 장치 내부 표현에 묶입니다. 그 대신 값의 복사본을 받고 읽기만 합니다. sensor_node는 정상 측정인지 판단하는 정보를 전달하지만 화면 문구의 언어와 소수점 자릿수를 결정하지 않습니다. 생산자 변경과 화면 변경이 서로 독립적인 테스트로 검토될 수 있는지 확인하면 책임 경계가 적절한지 판단할 수 있습니다.
발행 시각과 측정 시각을 분리합니다
미션은 앞 모듈의 측정 세 행을 최근 상태 유지 방식으로 20Hz 발행합니다. 측정은 0, 0.1, 0.2초에 있었고 마지막은 센서 실패입니다. 0.05초 발행에서도 측정 stamp_s는 0으로 남습니다. 0.95초 발행에서도 마지막 측정 시각은 0.2초입니다. pub_s와 pub_seq는 새로 추가하는 발행 정보입니다. 같은 상태를 두 번 전달했다고 독립적인 센서 관측 두 개가 생기는 것은 아닙니다.
최근 상태 유지가 유용한 이유는 화면 갱신 주기와 장치 입력 주기를 별도로 표현할 수 있기 때문입니다. 그러나 측정 시각까지 발행 시각으로 덮어쓰면 오래된 값이 계속 새 값으로 보입니다. 다음 시간 정렬 모듈에서 지연을 계산할 근거도 사라집니다. 제출 구성도에는 두 시각의 생산 위치를 별도로 표시하고, 새 메시지에 어떤 원본 필드가 그대로 남는지 명시합니다.
오류가 나면 먼저 경계를 찾습니다
후속 실습에서 FIELD missing stamp_s가 보이면 메시지 형태를 만든 경계를 확인합니다. NO_DATA는 최초 수신이 없다는 표시이고 센서가 고장났다는 단정이 아닙니다. INVALID는 결함 상태를 실제 수신한 화면 표시입니다. 이름이 틀려 아무것도 못 받은 상황과 오류 상태를 정상적으로 받은 상황을 구분해야 로그를 보고 다음 확인 대상을 정할 수 있습니다. 단순히 화면을 0.000으로 채우면 이 차이를 잃습니다.
설계 실습은 파일 변경 대신 구성도와 계약표를 제출합니다. 각 연결의 데이터 의미, 타입 라벨, 단위, 시간 기준, 실패 반응이 드러나야 합니다. 구성도에 실현되지 않은 ROS 실행기나 네트워크 재전송을 이미 구현한 것처럼 넣지 않습니다. 더 읽기의 ROS 소개는 시스템 배경을 확장하는 자료입니다. 이 레슨에서는 우리 프로젝트가 어느 경계까지 구현했는지 설명할 수 있으면 목표를 달성합니다.
사실 확인 자료: ROS 2 Jazzy 공식 문서 원본 — 노드의 역할과 프로세스 배치 기준에서 해당 기준을 확인할 수 있습니다.
따라하기
역할별 포트를 출력합니다
아래 코드는 역할표의 최소 골격입니다. Python 파일로 저장해 실행하고 구성도에 입력과 출력 방향을 옮깁니다.
ports = {"sensor_node": ("HAL JSONL", "/robot/range_status"),
"status_node": ("/robot/range_status", "screen"),
"drive_node": ("wheel request", "HAL set_wheels")}
for name, (source, target) in ports.items():
print(f"{name}: {source} -> {target}")실행 결과
sensor_node: HAL JSONL -> /robot/range_status status_node: /robot/range_status -> screen drive_node: wheel request -> HAL set_wheels
결함 값을 화면 값과 구분합니다
원본 값이 남아 있어도 결함 표시를 먼저 적용합니다. 두 줄의 표시를 계약표의 정상·실패 행에 연결합니다.
for valid, status in [(True, 0), (False, 2)]:
distance = 0.05
display = f"{distance:.3f}" if valid else "INVALID"
print(f"range={display} status={status}")실행 결과
range=0.050 status=0 range=INVALID status=2
구성도와 계약표를 제출합니다
sensor_node에서 status_node로 화살표를 그리고 토픽·타입·주기를 붙입니다. drive_node에는 별도 요청 경계를 둡니다. 측정 시각을 생성하는 HAL과 발행 시각을 생성하는 어댑터를 표시합니다. 화살표마다 단위와 실패 동작을 적은 표를 함께 제출합니다.
확인 문제
실습
세 역할 구성도와 계약표를 제출합니다. 센서→화면 연결의 이름·타입·거리 단위·두 시각·0.05초 주기, 구동 요청의 별도 경계, NO_DATA와 INVALID의 차이를 포함합니다. 표시 노드가 모터 출력을 바꾸지 않으며 PC 모델과 실제 ROS 실행 검증 범위를 구분하는지 검토합니다.
더 읽기
면접 질문
- ROS 2 토픽과 서비스, 액션의 선택 기준을 설명해 주시면 됩니다.