11.9 이기종 언어 기반 노드와의 다이렉트 연동

11.9 이기종 언어 기반 노드와의 다이렉트 연동

초창기 ROS2 시대에, C++ 로 짠 로봇을 웹 브라우저나 클라우드의 데이터베이스와 연결하려면 로봇 안에 rosbridge 내지는 FastAPI 릴레이 서버를 두어 번씩 칭칭 감아 띄워야만 했다.

Zenoh 는 그딴 허접한 릴레이 서버를 용납하지 않는다.
로봇이 rclcpp(C++)로 돌고 있어도, 외부의 순수 C, Rust, 방구석 웹사이트의 TypeScript(JavaScript) 코드가 마치 “자신들이 원래부터 ROS2 노드인 양” 로봇의 코어 네트워크에 스며들어 직접 통신하는 기적적인 “이기종 크로스오버(Cross-over)” 코딩 런북이다.

1. ROS2 시스템과 순수 C Zenoh 애플리케이션 간의 직접 통신

10장에서 배운 마이크로컨트롤러(Pico)나 초경량 리눅스 보드 환경이다. 이 보드들은 기껏해야 16MB 플래시 메모리밖에 없어서, 2GB 짜리 통뼈 리눅스 로봇(ROS2) 안에 들어갈 수가 없다.

1.0.1 [Runbook] ROS2 매핑 바이패스(Bypass) 전술 (C 편)

ROS2 노드가 11.3 장의 zenoh-bridge-dds 나 11.4 장의 rmw_zenoh 를 통해 Zenoh 망으로 들어오면, 토픽 앞부분에 ros2/rt/... 가 강제 부여된다는 사실을 잊었는가? 이것을 역이용한다.

[순수 C 코드 측면] (ROS2 라이브러리 일절 없음)

#include "zenoh.h" // 오직 순수 Zenoh C 만 사용!

void on_ros2_lidar_data(const z_sample_t *s, void *ctx) {
    // 3. 우주선(로봇)에서 보낸 라이다 Raw 바이트가 이곳으로 직행한다!
    printf("ROS2 Lidar frame received: %d bytes\n", s->payload.len);
}

int main() {
    z_config_t config = z_config_default();
    z_owned_session_t s = z_open(z_move(config));

    // 1. ROS2 노드의 구독자(Subscriber)인 척 위장한다!
    // "ros2/rt" 를 붙여서 ROS2 C++ 노드가 발행한 /scan 토픽을 훔쳐 듣는다.
    z_owned_closure_sample_t cb;
    z_closure(&cb, on_ros2_lidar_data, NULL, NULL);
    z_declare_subscriber(z_loan(s), z_keyexpr("ros2/rt/scan"), z_move(cb), NULL);

    // 2. ROS2 퍼블리셔(Publisher)인 척 위장한다!
    // 내가 C 코드로 보낸 패킷이 로봇 안에선 "/cmd_vel" 조이스틱 토픽으로 변역된다!
    z_publisher_t pub;
    z_declare_publisher(z_loan(s), z_keyexpr("ros2/rt/cmd_vel"), &pub);
    
    // Twist 메시지의 이진(CDR) 바이트 배열을 구워서 쏴버린다.
    uint8_t twist_cdr_bytes[] = { ... }; 
    z_publisher_put(z_loan(pub), twist_cdr_bytes, sizeof(twist_cdr_bytes), NULL);
    
    while(1) { sleep(1); } // 무한 대기
}

아키텍트의 결론:
ROS2 의 족쇄(rclcpp 의 종속성) 가 아예 없는 초소형 칩 조차도, 자신이 보내는 토픽 이름에 ros2/rt/ 접두어를 붙이고 페이로드를 ROS2 와 동일한 규격(C 구조체 배열)으로 맞춰주기만 하면, 거대한 로봇의 심장 통신망에 완벽한 스파이(Spy Node)로 잠입할 수 있다.

2. ROS2 시스템과 순수 Rust Zenoh 애플리케이션 간의 직접 통신

백엔드 인프라 개발자들은 안정성과 스피드를 위해 Rust 를 쓰려 하지만, 로봇 팀은 여전히 C++ 에 갇혀있다.
관제 서버(Rust)가 로봇(C++)을 통제하는 과정이다.

2.0.1 [Runbook] Rust 의 역변환 직렬화 (Deserialization) 전술

순수 Rust 애플리케이션 쪽에서 수신 코드를 짠다.

use zenoh::prelude::r#async::*;
use cdr::{CdrBe, Infinite}; // ROS2는 CDR 이라는 무식한 이진 레이아웃을 쓴다

// ROS2의 std_msgs/msg/String 과 똑같은 이진 메모리 구조를 선언!
#[derive(serde::Deserialize, Debug)]
struct Ros2StringMsg {
    data: String,
}

#[async_std::main]
async fn main() {
    let session = zenoh::open(zenoh::config::default()).await.unwrap();

    // ROS2 의 /chatter 토픽 훔쳐보기
    let subscriber = session.declare_subscriber("ros2/rt/chatter").await.unwrap();

    while let Ok(sample) = subscriber.recv_async().await {
        // ROS2가 뱉은 날것의 더러운 바이트 덩어리를 
        // Rust 의 깔끔한 구조체(Struct)로 역직렬화(Deserialize) 해버린다!
        let msg: Result<Ros2StringMsg, _> = cdr::deserialize_from(
            &sample.value.payload.contiguous()[..],
            cdr::size::Infinite
        );

        if let Ok(data) = msg {
            println!("🤖 로봇 왈: {}", data.data);
        }
    }
}

이로서 무거운 데이터베이스 저장 관리, 도커 래핑(Wrapping) 등의 현대적인 백엔드 기술(Rust 생태계)을, 과거의 틀(C++) 에 갇힌 로봇 팀의 산출물과 “프로토콜 레벨(Zero-overhead)” 에서 융합시켜버리는 데 성공한 것이다.

3. TypeScript 기반의 웹 프론트엔드에서 ROS2 토픽 직접 제어 및 구독 (rosbridge 대체)

“웹 대시보드에서 조이스틱을 움직이고, 로봇의 영상(/camera)을 브라우저 캔버스로 보고 싶습니다.”

전형적인 이 요구사항을 위해 사람들은 무거운 rosbridge_suite 를 서버에 띄우고, JSON 으로 변환(Serialization)하는 엄청난 CPU 오버헤드로 인해 초당 프레임이 5Hz 단위로 뚝뚝 끊기는 참사를 겪어왔다.

3.0.1 [Runbook] 브라우저 다이렉트 엑세스 (Zenoh-REST 통과) 전술

Node.js 백엔드 서버도 필요 없다. 크롬 브라우저(TypeScript) 가 즉각적으로 클라우드의 Zenoh 라우터(HTTP 플러그인 켜짐 상태) 에 접속해 버리는 마술이다.

1. Zenoh 라우터(REST 플러그인) 세팅

## AWS 라우터: 브라우저가 접속할 수 있게 HTTP 포트 8000번 개방!
zenohd --rest-http-port 8000

2. 브라우저용 순수 TypeScript 코드
로봇 전문가가 아닌 “일반 파트타임 프론트엔드 개발자” 에게 이 코드만 넘겨주면 개발이 끝난다.

// [이 코드는 리액트(React) 웹 브라우저 안에서 돈다]
import("@zenoh/zenoh-rest-cli").then((zenoh) => {
    
    // AWS 에 떠있는 라우터의 HTTP 구명에 붙는다
    const session = await zenoh.open({ endpoint: "http://aws-router-ip:8000" });

    // 1. [로봇 조이스틱 쏘기 (Publisher 위장)]
    // 방향키 누를 때마다 이 코드가 발사됨
    const joyKey = "ros2/rt/cmd_vel";
    const twistCdrPacket = ... // (Twist 이진 배열 직렬화 라이브러리 연동)
    await session.put(joyKey, twistCdrPacket);
    
    // 2. [로봇 상태 듣기 (Subscriber 위장)]
    // 브라우저의 WebSocket을 타고 로봇 센서 데이터가 초당 30번 폭격처럼 쏟아져들어온다!
    const batterySub = await session.subscribe("ros2/rt/battery");
    batterySub.on('data', (sample) => {
        let battery_level = decodeCdrBytes(sample.value);
        updateReactUI(battery_level);
    });
});

ROS2 노드와 인터넷 브라우저 사이를 가로막던 통신의 만리장성이 붕괴되었다.
백엔드 중계 서버도, JSON 파싱 오버헤드도 없다. 웹 브라우저는 그 자체로 거대한 글로벌 스케일 로봇 군집망의 일개 노드(Node) 가 되어 로봇과 완벽히 동일선상(Peer) 의 지위에서 대화를 진행한다.