토픽이 보이는 것부터 작업 성공까지
브리지는 시뮬레이터의 상태/센서와 ROS 2 인터페이스를 연결한다. 통신이 발견되는 것, 메시지가 도착하는 것, 숫자가 올바르게 해석되는 것, 물리 상태가 목표를 따라가는 것은 다른 단계다. 그래프에서 토픽 이름을 봤다는 이유만으로 마지막 단계까지 통과했다고 쓰지 않는다. 발행자/구독자·타입·QoS·이름/namespace·도메인과 전달량을 확인한 뒤 내용과 동작을 검사한다.
가상 설계에서 제어 노드는 /sim/joint_command를 발행하고 시뮬레이터 어댑터는 이를 구독하여 정해진 관절 제어 입력에 연결한다고 하자. 시뮬레이터 상태 발행자는 /sim/joint_states를 보내며 RSP/기록기가 읽는다. 명령 발행자와 상태 발행자는 역할이 다르다. 같은 숫자를 양쪽에 복사해도 실제 추종 증거가 되지 않는다. 상태를 장면 위치로 직접 설정하는 미러링과 힘/drive를 통해 목표를 따르는 물리 실험도 구분한다.
Isaac Sim 6.2의 공식 Joint Control 예제는 관절 상태/명령과 articulation 제어 연결을 보여 준다. 예제에서 JointState를 명령 운반에 쓰는 것은 그 소비자가 정의한 계약이며 표준 메시지가 자동으로 명령 의미를 갖는 것은 아니다. 실제 프로젝트는 궤적/action/ros2_control 등 필요한 완료·취소·제한 계약을 선택한다. 여기서는 학습용 어댑터의 절대 위치 목표만 해석한다.
시계와 권한도 정한다. 시뮬레이션이 /clock을 소유하고 관련 노드가 그 시계를 쓰도록 구성하는 경우, 시스템 시각을 같은 숫자 축으로 취급하지 않는다. reset/일시정지 시 명령 만료와 상태 기록 정책도 필요하다. 두 주체가 같은 TF child를 서로 다른 값으로 발행하거나 두 제어기가 같은 관절을 동시에 지배하지 않도록 담당자를 정한다. 연결과 시간의 기본은 M05.03/M05.06을 보충 경로로 사용한다.
관절 명령은 순서와 신선도를 검사한다
합성 절대 위치 명령의 name=[elbow,shoulder], position=[0.2,0.5]라 하자. 두 관절 모두 rad 단위 회전 관절이고 유효 범위는 [−1,1] rad다. 어댑터 내부 순서가 [shoulder,elbow]면 값은 [0.5,0.2]로 재정렬한다. 이름/값 길이·중복/누락·유한 값·범위·모드와 단위를 검사한다. 원본 관절 이름과 변환된 USD 이름의 대응표도 필요하다. degree 속성과 rad API를 혼동하거나 그리퍼 m 값을 rad로 읽지 않는다.
이 어댑터의 별도 계약에서 명령 stamp는 ROS 시뮬레이션 시각 1.10 s, 현재 시각은 1.12 s, 허용 나이는 0~0.05 s라고 명시하자. 나이는 0.02 s이므로 그 조건은 통과다. 현재가 1.18 s면 0.08 s로 실패다. 미래 시각도 이 계약에서는 거부한다. 이것이 모든 ROS 노드의 공통 TTL 규칙이라는 뜻은 아니다. TTL은 유효 기간이며 애플리케이션이 명시·검사하는 조건이다.
상태 로그의 shoulder=0.4 rad가 stamp=1.08 s라면 1.10 s에 생성한 목표 0.5와 차이 0.1을 계산할 수는 있어도 목표 적용 뒤의 추종 오차로 확정할 수 없다. 관측이 명령보다 앞서기 때문이다. 목표 적용 시각과 이후 상태·지연·모드/포화/오류를 확인해야 한다. topic echo로 값이 보이는 것과 작업 완료 판정이 다른 이유가 여기에 있다. 상태 미제공을 0으로 채우거나 오래된 상태 stamp를 현재로 덮어쓰지 않는다.
카메라 Prim은 영상 토픽이 아니다
카메라 Prim의 위치/광학 설정에서 렌더 결과를 얻고 이를 메시지로 변환·발행하는 경로가 필요하다. render product는 지정 카메라의 렌더 데이터를 얻는 연결점이며 viewport에서 방을 본다고 그 영상이 의도한 ROS 센서 토픽에 발행되는 것은 아니다. 카메라 경로, render product의 연결/활성, 데이터 종류와 발행 주기를 확인한다. 6.2 카메라 문서는 기존 frameSkipCount 대신 OmniSensorAPI가 적용된 카메라의 omni:sensor:tickRate를 쓰는 변경을 설명한다. 다른 버전의 옵션 이름을 그대로 옮기지 않는다.
ROS Image의 encoding은 채널 의미/순서와 값 크기를 나타낸다. rgb8이면 한 픽셀의 RGB 세 채널이 각각 8 bit다. bgr8과 같은 색 순서라고 해석하면 빨강/파랑이 뒤바뀔 수 있다. width/height는 열/행 수이고 step은 행 하나의 바이트 수다. 패딩이 있을 수 있으므로 언제나 step=width×채널 수라고 가정하지 않는다. data 크기는 step×height와 맞는지 검사한다.
합성 영상이 640×480, rgb8, 패딩 없는 step=1920 byte라면 한 영상은 1920×480=921600 byte다. 30 Hz면 원시 영상 payload는 초당 27648000 byte, 십진 단위 약 27.648 MB/s다. 네트워크 헤더·다른 토픽·복사·압축 전후 비용은 포함하지 않았다. 이 계산만으로 실제 대역폭이나 지연을 측정했다고 할 수 없다. step이 1936이라면 패딩을 포함한 크기를 다시 계산해야 한다.
영상·보정·TF가 같은 관측을 가리키는가
Image와 CameraInfo의 header는 영상 취득 시각과 optical frame을 설명한다. optical 축은 +x가 영상 오른쪽, +y가 아래쪽, +z가 전방이다. ROS의 일반 몸체 프레임과 다를 수 있으므로 이름만 바꿔 축을 맞췄다고 하지 않는다. CameraInfo는 이미지와 맞는 해상도/내부 파라미터/왜곡 정보를 제공해야 하고 해당 시각의 TF로 다른 프레임과 연결한다. Image와 연결된 CameraInfo의 frame_id가 충돌하면 계약 위반이다.
640×480 영상에 다른 크기에서 얻은 내부 파라미터를 근거 없이 재사용하거나 RGB/깊이의 다른 관측 시각을 같게 취급하면 영상이 보여도 좌표 해석은 틀릴 수 있다. CameraInfo가 도착했다고 보정이 유효하다는 증거도 아니며 K[0]=0은 공식 메시지에서 미보정 표시로 해석할 수 있다. 핀홀 계산은 M12.02에서 자세히 다룬다. 지금은 ‘영상이 보임’과 ‘프레임/보정/시각이 맞는 관측’의 차이를 검사한다.
기존 1.5 브리지와 4.2 프레임을 연결하면 통신→입력 계약→시뮬레이션 적용→시간 있는 상태/영상의 순서로 근거를 남길 수 있다. 아래 활동은 실제 연결 로그가 아니라 위 설계의 합성 예다.