Curieux.JY
  • Post
  • Note
  • Jung Yeon Lee

On this page

  • 1 1๏ธโƒฃ Introduction
    • 1.1 ํ•ต์‹ฌ ํŠน์ง• (์š”์  ์ •๋ฆฌ)
    • 1.2 ์•„ํ‚คํ…์ฒ˜์™€ ๋™์ž‘ ๋ฐฉ์‹(๊ฐ„๋‹จํ•œ ๊ฐœ๋…)
    • 1.3 ์„ค์น˜
    • 1.4 ์˜ˆ์ œยท์ƒํƒœ๊ณ„
    • 1.5 ์–ธ์ œ DORA๋ฅผ ์“ฐ๋ฉด ์ข‹๋‚˜?
    • 1.6 ์žฅ๋‹จ์  / ์ฃผ์˜์‚ฌํ•ญ
  • 2 2๏ธโƒฃ ๊ฐ„๋‹จํ•œ Dora ๋…ธ๋“œ(Rust) ํ…œํ”Œ๋ฆฟ + ROS2 ์—ฐ๋™ ์•„ํ‚คํ…์ฒ˜(๋ธŒ๋ฆฟ์ง€ ์˜ˆ์‹œ ํฌํ•จ)
    • 2.1 ๋ชฉํ‘œ ์š”์•ฝ
    • 2.2 Rust ๋…ธ๋“œ ํ…œํ”Œ๋ฆฟ (์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ ์˜ˆ์ œ)
    • 2.3 ๊ฐ„๋‹จํ•œ dataflow.yml ์˜ˆ์‹œ (graphs/dataflow.yml)
    • 2.4 ROS2์™€์˜ ์—ฐ๋™ โ€” ์•„ํ‚คํ…์ฒ˜ & ์‹คํ–‰ ํ๋ฆ„ (๊ตฌ์ฒด์ )
      • 2.4.1 ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š” (ํ…์ŠคํŠธ ๋‹ค์ด์–ด๊ทธ๋žจ)
      • 2.4.2 ๊ตฌ์ฒด์  ์—ฐ๋™ ์˜ˆ์‹œ (Rust ์ชฝ ์ฝ”๋“œ ํ๋ฆ„ ์š”์•ฝ)
      • 2.4.3 Python ์˜ˆ์‹œ(๊ฐ„๋‹จ) โ€” ๋ฌธ์„œ์—์„œ ๊ฐ€์ ธ์˜จ ํ๋ฆ„
    • 2.5 ๋ฐฐํฌ/์‹คํ–‰ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (์‹ค๋ฌด ํŒ)
  • 3 3๏ธโƒฃ Advanced
    • 3.1 ์š”์•ฝ
    • 3.2 A. ์™„์ „ํ•œ โ€œ๊ฐ„๋‹จํ•œ Dora ๋…ธ๋“œ (Rust) ํ…œํ”Œ๋ฆฟโ€ (์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์˜ˆ์ œ)
      • 3.2.1 Rust ๋…ธ๋“œ(์ฝ”๋“œ) โ€” rust-heartbeat-node
      • 3.2.2 ๊ฐ„๋‹จํ•œ dataflow.yml (dora-workspace/graphs/dataflow.yml)
    • 3.3 B. ROS2์™€ ์—ฐ๋™ํ•˜๋Š” ๊ตฌ์ฒด์  ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„(๋ธŒ๋ฆฟ์ง€ ์˜ˆ์‹œ ํฌํ•จ)
      • 3.3.1 ROS2 Python ํผ๋ธ”๋ฆฌ์…”/์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ (ํ† ์ด)
      • 3.3.2 Dora โ†”๏ธŽ ROS2 ๋ธŒ๋ฆฌ์ง€๋ฅผ ํ†ตํ•œ ๋งคํ•‘(์„ค๊ณ„ & ์˜ˆ์‹œ)
    • 3.4 C. ์ปจํ…Œ์ด๋„ˆํ™” (Docker + docker-compose) โ€” ROS2 + Dora + Rust node
      • 3.4.1 docker/Dockerfile.dora โ€” dora ๋ฐ๋ชฌ/CLI (๊ฐ„๋‹จ: release ๋ฐ”์ด๋„ˆ๋ฆฌ ์‚ฌ์šฉ)
      • 3.4.2 docker/Dockerfile.ros2 โ€” ROS2 (humble) ์ปจํ…Œ์ด๋„ˆ
      • 3.4.3 docker/Dockerfile.rustnode โ€” Rust ๋…ธ๋“œ๋ฅผ ๋นŒ๋“œํ•ด ๋Ÿฐํƒ€์ž„ ๋ฐ”์ด๋„ˆ๋ฆฌ๋งŒ ๋‹ด๋Š” ๋‹ค๋‹จ๊ณ„ ๋นŒ๋“œ
      • 3.4.4 docker-compose.yml (๊ฐ„๋‹จ ํ…œํ”Œ๋ฆฟ)
    • 3.5 ์ „์ฒด ์‹คํ–‰ ์ ˆ์ฐจ
    • 3.6 ๊ตฌํ˜„/์šด์˜ ํŒ & ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…
    • 3.7 ์ถ”๊ฐ€ ์ž๋ฃŒ(๋ฐ”๋กœ ํ™•์ธํ•ด์•ผ ํ•  ๋ฌธ์„œ ๋งํฌ ์š”์•ฝ)

๐ŸงฉDORA

dora
ros2
rust
2025
Making robotic applications fast and simple!
Published

October 13, 2025

AI is booming! Robotic framework however hasnโ€™t changed much in yearsโ€ฆ I totally agree!


1 1๏ธโƒฃ Introduction

DORA(Dataflow-Oriented Robotic Architecture)๋Š” ๋กœ๋ด‡/์ž„๋ฒ ๋””๋“œ AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ง€ํ–ฅ ๋ฏธ๋“ค์›จ์–ด๋กœ, ๋…ธ๋“œ(์—ฐ์‚ฐ)๋ฅผ ๊ทธ๋ž˜ํ”„(ํŒŒ์ดํ”„๋ผ์ธ)๋กœ ์ •์˜ํ•˜์—ฌ ์ €์ง€์—ฐ, ์กฐํ•ฉ๊ฐ€๋Šฅ(composable), ๋ถ„์‚ฐ ์‹คํ–‰์„ ๋ชฉํ‘œ๋กœ ์„ค๊ณ„๋œ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.

1.1 ํ•ต์‹ฌ ํŠน์ง• (์š”์  ์ •๋ฆฌ)

  • ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ๋ชจ๋ธ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋…ธ๋“œ(ํ”„๋กœ์„ธ์Šค/๋ชจ๋“ˆ)์™€ ์—ฃ์ง€(๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ)๋กœ ๊ตฌ์„ฑ๋œ ๊ทธ๋ž˜ํ”„(ํŒŒ์ดํ”„๋ผ์ธ)๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๋…ธ๋“œ๋ฅผ ์‰ฝ๊ฒŒ ๊ต์ฒดยท์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ €์ง€์—ฐ(Zero-copy) ์„ค๊ณ„: ์ž์ฒด ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ์„œ๋ฒ„์™€ Apache Arrow๋ฅผ ํ™œ์šฉํ•ด ์ œ๋กœ ์นดํ”ผ(๋ณต์‚ฌ ์—†๋Š”) ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์„ ์ง€์›ํ•˜์—ฌ ๋†’์€ ์„ฑ๋Šฅ์„ ๋ƒ…๋‹ˆ๋‹ค.
  • ๋ฉ€ํ‹ฐ์–ธ์–ด/๋ฉ€ํ‹ฐ๋Ÿฐํƒ€์ž„: Rust๋กœ ํ•ต์‹ฌ์ด ๊ฐœ๋ฐœ๋˜์—ˆ์ง€๋งŒ, ๋…ธ๋“œ๋Š” Rust/Python/C ๋“ฑ ๋‹ค์–‘ํ•œ ์–ธ์–ด๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋…ธ๋“œ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: dora-node-api ํฌ๋ ˆ์ดํŠธ ๋ฌธ์„œ).
  • ๋ถ„์‚ฐยท์กฐํ•ฉ ๊ฐ€๋Šฅ: ๋กœ์ปฌ ํ”„๋กœ์„ธ์Šค๋ฟ ์•„๋‹ˆ๋ผ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ•ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜คํ”ˆ ์†Œ์Šคยท๋ผ์ด์„ ์Šค: GitHub์— ๊ณต๊ฐœ๋˜์–ด ์žˆ์œผ๋ฉฐ Apache-2.0 ๋ผ์ด์„ ์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (์Šคํƒ€/ํฌํฌ ๋“ฑ ํ™œ๋™๋„ ํ™œ๋ฐœ).

1.2 ์•„ํ‚คํ…์ฒ˜์™€ ๋™์ž‘ ๋ฐฉ์‹(๊ฐ„๋‹จํ•œ ๊ฐœ๋…)

  1. ๋…ธ๋“œ(node): ์„ผ์„œ ์ž…๋ ฅ, ๋ชจ๋ธ ์ถ”๋ก , ํ”Œ๋ž˜๋„ˆ, ๋กœ๊ทธ ๋“ฑ ๊ฐ๊ฐ์˜ ์—ฐ์‚ฐ ๋‹จ์œ„๋ฅผ ๋…ธ๋“œ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ๋…ธ๋“œ๋Š” ์ž…๋ ฅ์„ ๋ฐ›๊ณ  ์ถœ๋ ฅ(๋˜๋Š” ์—ฌ๋Ÿฌ ์ถœ๋ ฅ)์„ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  2. ์˜คํผ๋ ˆ์ดํ„ฐ(operator): ๋…ธ๋“œ ๋‚ด๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋ผ์šฐํŒ…ํ•˜๋Š” ์ž‘์€ ์—ฐ์‚ฐ ๋‹จ์œ„๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ๊ทธ๋ž˜ํ”„ ์ •์˜: dataflow.yml ๊ฐ™์€ ๊ทธ๋ž˜ํ”„ ์ •์˜ ํŒŒ์ผ๋กœ ๋…ธ๋“œ ๊ฐ„ ์—ฐ๊ฒฐ(ํŒŒ์ดํ”„๋ผ์ธ)์„ ์„ ์–ธํ•˜๊ณ , DORA ๋Ÿฐํƒ€์ž„์ด ์ด๋ฅผ ํ•ด์„ํ•ด ๋…ธ๋“œ ๊ฐ„ ํ†ต์‹ ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ฐ์ดํ„ฐ ํฌ๋งท: ์„ฑ๋Šฅ์„ ์œ„ํ•ด Arrow ํฌ๋งท์„ ์šฐ์„  ์ถ”์ฒœํ•˜๊ณ , ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋ฐ์ดํ„ฐ ๋ฒ„ํผ๋ฅผ ์ฃผ๊ณ ๋ฐ›์•„ ๋ณต์‚ฌ ๋น„์šฉ์„ ์ค„์ž…๋‹ˆ๋‹ค.

(์š”์•ฝํ•˜๋ฉด: ์ž‘์€ ๋…๋ฆฝ ํ”„๋กœ์„ธ์Šค๋“ค์„ ๊ทธ๋ž˜ํ”„์ฒ˜๋Ÿผ ์—ฐ๊ฒฐํ•ด ๋ฐ์ดํ„ฐ๊ฐ€ ํ๋ฅด๋„๋ก ํ•˜๊ณ , ๊ทธ ์ „๋‹ฌ์„ ๊ณ ์„ฑ๋Šฅ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.)

1.3 ์„ค์น˜

  • ๋ฐฐํฌํŒ(๋ฆด๋ฆฌ์Šค ZIP)์„ ๋‹ค์šด๋กœ๋“œํ•ด $HOME/bin ๋“ฑ ๊ฒฝ๋กœ์— ๋„ฃ๊ณ  dora --help๋กœ ์„ค์น˜ ํ™•์ธ. ([dora-rs.ai][6])
  • Rust๋กœ ๋…ธ๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ  Cargo.toml์— ๋…ธ๋“œ/์˜คํผ๋ ˆ์ดํ„ฐ ๋ฉค๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•œ ๋’ค ์˜ˆ์‹œ์ฒ˜๋Ÿผ ๋…ธ๋“œ ์†Œ์Šค(๋ฐ”์ด๋„ˆ๋ฆฌ)์™€ sink(๋กœ๊ทธ ๋“ฑ)๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. dora-node-api๋ฅผ ํ†ตํ•ด ์ž…๋ ฅ/์ถœ๋ ฅ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฌธ์„œ์˜ โ€œGetting startedโ€ ๊ฐ€์ด๋“œ๊ฐ€ ๋‹จ๊ณ„๋ณ„ ์˜ˆ์ œ๋ฅผ ์ œ๊ณตํ•˜๋‹ˆ ๋”ฐ๋ผ ํ•˜๋ฉด ๊ธฐ๋ณธ ํŒŒ์ดํ”„๋ผ์ธ์„ ์‹คํ–‰ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1.4 ์˜ˆ์ œยท์ƒํƒœ๊ณ„

  • dora-drives: ์ดˆ์‹ฌ์ž์šฉ ์ž์œจ์ฃผํ–‰ ํŠœํ† ๋ฆฌ์–ผ๋กœ, DORA๋กœ ์ž์œจ์ฃผํ–‰ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋งŒ๋“ค์–ด๋ณด๋Š” ๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ํ•™์Šตยท๋ฐ๋ชจ์šฉ์œผ๋กœ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • dora-lerobot, ๊ธฐํƒ€ ๋ฐ๋ชจ/ํ—ˆ๋ธŒ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋“ค์ด ์žˆ๊ณ , ๊ณต์‹ ์‚ฌ์ดํŠธ์˜ Guides/Blog/Examples๋ฅผ ํ†ตํ•ด ๋ฐ๋ชจ(์˜ˆ: Reachy ๋กœ๋ด‡ ๋ฐ๋ชจ)์™€ ์—ฐ๋™ ์‚ฌ๋ก€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1.5 ์–ธ์ œ DORA๋ฅผ ์“ฐ๋ฉด ์ข‹๋‚˜?

  • ์—ฌ๋Ÿฌ ์„ผ์„œยท๋ชจ๋“ˆ(์นด๋ฉ”๋ผ, ๋ผ์ด๋‹ค, IMU, ๋ชจ๋ธ ์ถ”๋ก  ๋“ฑ)์„ ์กฐํ•ฉํ•ด ์ €์ง€์—ฐ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ•ด์•ผ ํ•  ๋•Œ: ๋ฐ์ดํ„ฐ ํฌ๋งท(Arrow)๊ณผ ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ์ „๋‹ฌ์ด ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฉ€ํ‹ฐ์–ธ์–ด(์˜ˆ: ์ผ๋ถ€ ๋ชจ๋“ˆ์€ Python, ์„ฑ๋Šฅ ๋ชจ๋“ˆ์€ Rust)๋กœ ๊ตฌ์„ฑ๋œ ์‹œ์Šคํ…œ์„ ํ•˜๋‚˜์˜ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๋ฌถ์–ด์•ผ ํ•  ๋•Œ.
  • ๋ถ„์‚ฐ ํ™˜๊ฒฝ(๋กœ์ปฌ + ์—ฃ์ง€ + ํด๋ผ์šฐ๋“œ)์—์„œ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋ถ„ํ•ดยท๋ฐฐ์น˜ํ•ด ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ.

1.6 ์žฅ๋‹จ์  / ์ฃผ์˜์‚ฌํ•ญ

  • ์žฅ์ 
    • ์„ฑ๋Šฅ(์ œ๋กœ์นดํ”ผ)๊ณผ ์•ˆ์ „์„ฑ(์ฃผ์š” ์ฝ”๋“œ๊ฐ€ Rust๋กœ ์ž‘์„ฑ)์œผ๋กœ ๋†’์€ ์ฒ˜๋ฆฌ๋Ÿ‰๊ณผ ๋‚ฎ์€ ์ง€์—ฐ์„ ๊ธฐ๋Œ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ชจ๋“ˆํ™”๊ฐ€ ์ž˜ ๋˜์–ด ์žˆ์–ด ๋ณต์žกํ•œ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊น”๋”ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹จ์  / ๊ณ ๋ ค์‚ฌํ•ญ
    • ํ•™์Šต๊ณก์„ : ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ๊ฐœ๋…๊ณผ Rust ๊ธฐ๋ฐ˜ ์›Œํฌ์ŠคํŽ˜์ด์Šค/ํฌ๋ ˆ์ดํŠธ ๊ตฌ์กฐ์— ์ต์ˆ™ํ•ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ์ƒํƒœ๊ณ„ ์„ฑ์ˆ™๋„: ROS2 ๋ธŒ๋ฆฟ์ง€ ๋“ฑ ํ™•์žฅ๋“ค์ด ์žˆ์ง€๋งŒ(๋ฌธ์„œ/์˜ˆ์ œ ์ฐธ๊ณ ) ํŠน์ • ํ•˜๋“œ์›จ์–ด ๋“œ๋ผ์ด๋ฒ„๋‚˜ ์ƒ์šฉ ๋ฏธ๋“ค์›จ์–ด์™€์˜ ์ง์ ‘ ํ†ตํ•ฉ์€ ์ถ”๊ฐ€ ์ž‘์—…์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ฐ€์ด๋“œยท์˜ˆ์ œ ํ™œ์šฉ ๊ถŒ์žฅ).

๋น ๋ฅธ ์‹ค์Šต ํŒ

  • ๋จผ์ € ๊ณต์‹ โ€œGetting startedโ€ ์˜ˆ์ œ๋ฅผ ๋”ฐ๋ผํ•ด dataflow.yml๊ณผ ๊ฐ„๋‹จํ•œ Rust ๋…ธ๋“œ๋ฅผ ์‹คํ–‰ํ•ด ๋ณด์„ธ์š”. dora-node-api ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด ๋…ธ๋“œ ์ž…์ถœ๋ ฅ ์ฒ˜๋ฆฌ ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2 2๏ธโƒฃ ๊ฐ„๋‹จํ•œ Dora ๋…ธ๋“œ(Rust) ํ…œํ”Œ๋ฆฟ + ROS2 ์—ฐ๋™ ์•„ํ‚คํ…์ฒ˜(๋ธŒ๋ฆฟ์ง€ ์˜ˆ์‹œ ํฌํ•จ)

์•„๋ž˜๋Š” (1) dora-node-api ๋ฅผ ์‚ฌ์šฉํ•œ ์ตœ์†Œ ์‹คํ–‰ ๊ฐ€๋Šฅ Rust ๋…ธ๋“œ ํ…œํ”Œ๋ฆฟ๊ณผ ์‹คํ–‰/๋ฐฐํฌ ๋ฐฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  (2) ROS2 โ‡„ Dora ์—ฐ๋™(ros2-bridge)์„ ํฌํ•จํ•œ ๊ตฌ์ฒด์  ์•„ํ‚คํ…์ฒ˜์™€ ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ณต์‹ ๋ฌธ์„œ/๋ ˆํผ๋Ÿฐ์Šค๋„ ํ•จ๊ป˜ ํ‘œ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๊ฐœ๋…๊ณผ ๋ช…๋ น์€ Dora ๊ณต์‹ ๋ฌธ์„œยทAPI ๋ฌธ์„œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

2.1 ๋ชฉํ‘œ ์š”์•ฝ

  • Rust๋กœ ๊ฐ„๋‹จํ•œ Dora ๋…ธ๋“œ๋ฅผ ๋งŒ๋“ค์–ด output ํ† ํ”ฝ(๋ฐ์ดํ„ฐ์•„์ด๋””)์œผ๋กœ ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  • ์ด Dora ๋…ธ๋“œ๋ฅผ dora ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ(graph) ์— ๋“ฑ๋กํ•ด ์‹คํ–‰ํ•˜๊ณ , ํ•„์š”ํ•˜๋ฉด ROS2์™€ dora-ros2-bridge๋ฅผ ํ†ตํ•ด ํ† ํ”ฝ์„ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

2.2 Rust ๋…ธ๋“œ ํ…œํ”Œ๋ฆฟ (์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ ์˜ˆ์ œ)

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ (์˜ˆ์‹œ)

my_first_dataflow/
โ”œโ”€ rust-heartbeat-node/
โ”‚  โ”œโ”€ Cargo.toml
โ”‚  โ””โ”€ src/
โ”‚     โ””โ”€ main.rs
โ””โ”€ graphs/
   โ””โ”€ dataflow.yml

Cargo.toml (rust-heartbeat-node/Cargo.toml)

[package]
name = "rust-heartbeat-node"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0"
tokio = { version = "1", features = ["macros","rt-multi-thread"] }
futures = "0.3"
dora-node-api = "0.3"        # crates.io / docs.rs ๊ธฐ์ค€; ์‹ค์ œ ๋ฒ„์ „์€ ์ตœ์‹  ํ™•์ธ ๊ถŒ์žฅ
dora-core = "0.3"

src/main.rs

use anyhow::Result;
use std::time::Duration;
use futures::StreamExt;
use tokio::time::sleep;

use dora_node_api::{DoraNode, MetadataParameters};
use dora_core::config::DataId;

#[tokio::main]
async fn main() -> Result<()> {
    // Dora ๋Ÿฐํƒ€์ž„(๋ฐ๋ชฌ)์ด ์„ค์ •ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ ๋…ธ๋“œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
    // (dora๊ฐ€ ๋…ธ๋“œ๋ฅผ spawn/๊ด€๋ฆฌํ•˜๋„๋ก dataflow.yml๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ)
    let (mut node, mut events) = DoraNode::init_from_env()
        .expect("failed to init Dora node from env");

    // dataflow.yml์—์„œ ์ •์˜ํ•œ output id์™€ ์ผ์น˜์‹œํ‚ต๋‹ˆ๋‹ค.
    let output_id = DataId::from("heartbeat".to_owned());

    let mut counter: u64 = 0;

    loop {
        // ์ „์†กํ•  ๋ฐ”์ดํŠธ(์˜ˆ: u64 ๋ฆฌํ‹€์—”๋””์–ธ)
        let bytes = counter.to_le_bytes();

        node.send_output_raw(
            output_id.clone(),
            MetadataParameters::default(),
            bytes.len(),
            |out_buf| {
                out_buf.copy_from_slice(&bytes);
            }
        )?;

        println!("Sent heartbeat {}", counter);
        counter += 1;

        // ์ด๋ฒคํŠธ(์ž…๋ ฅ ๋“ฑ)๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ(์—ฌ๊ธฐ์„  ๊ฐ„๋‹จํžˆ drain)
        // EventStream์€ ๋น„๋™๊ธฐ ์ŠคํŠธ๋ฆผ์ž…๋‹ˆ๋‹ค.
        while let Ok(Some(evt)) = tokio::time::timeout(Duration::from_millis(0), events.next()).await {
            // ์‹ค์ œ ๋…ธ๋“œ๋ผ๋ฉด Event::Input ๋“ฑ์„ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
            // (์—ฌ๊ธฐ์„œ๋Š” ์˜ˆ์‹œ๋ผ ๊ฐ„๋‹จํžˆ ๋กœ๊ทธ)
            println!("Got event: {:?}", evt);
        }

        sleep(Duration::from_millis(500)).await;
    }
}

์„ค๋ช…ยท์ฃผ์˜

  • DoraNode::init_from_env()๋Š” dora ๋ฐ๋ชฌ(๋˜๋Š” dora-coordinator) ๊ฐ€ ๋…ธ๋“œ๋ฅผ ์Šคํฐํ•  ๋•Œ ์„ค์ •ํ•ด ์ฃผ๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ฝ์–ด ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. (๋”ฐ๋ผ์„œ ๋ณดํ†ต์€ dora start graphs/dataflow.yml ๋“ฑ์˜ ๋ฐฉ์‹์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค).
  • send_output_raw๋Š” ์ œ๋กœ-์นดํ”ผ(raw buffer closure) ๋ฐฉ์‹์œผ๋กœ ๋ฐ”์ดํŠธ ๋ฒ„ํผ๋ฅผ ์ฑ„์›Œ ์ „์†กํ•˜๋ฏ€๋กœ ๊ณ ์„ฑ๋Šฅ ์ „์†ก์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

2.3 ๊ฐ„๋‹จํ•œ dataflow.yml ์˜ˆ์‹œ (graphs/dataflow.yml)

์ด YAML์€ Dora์˜ ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ŠคํŽ™์„ ๊ฐ„๋‹จํžˆ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ํ•„๋“œ ์ด๋ฆ„/์˜ต์…˜์€ ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ(๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ŠคํŽ™)๋ฅผ ์ฐธ๊ณ ํ•ด ์กฐ์ •ํ•˜์„ธ์š”.

dataflow:
  nodes:
    - id: rust_heartbeat
      exec:
        # dora๊ฐ€ ์‹คํ–‰ํ•  ๋ฐ”์ด๋„ˆ๋ฆฌ(์ ˆ๋Œ€/์ƒ๋Œ€ ๊ฒฝ๋กœ ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆํ™”๋œ ์ด๋ฏธ์ง€ ๋“ฑ)
        path: "./rust-heartbeat-node/target/release/rust-heartbeat-node"
      outputs:
        - id: heartbeat
          format: bytes

์‹คํ–‰ (์˜ˆ์‹œ)

# 1) ๋นŒ๋“œ
cd my_first_dataflow/rust-heartbeat-node
cargo build --release

# 2) dora๋กœ ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰ (dora-cli ์„ค์น˜/์‚ฌ์šฉ๋ฒ•์€ Getting started ์ฐธ๊ณ )
# (์•„๋ž˜ ๋ช…๋ น์€ ์˜ˆ์‹œ; ์‹ค์ œ ํŒŒ์ผ ๊ฒฝ๋กœ์™€ dora-cli ํ™˜๊ฒฝ์— ๋”ฐ๋ฆ…๋‹ˆ๋‹ค)
dora start graphs/dataflow.yml --attach

์ฐธ๊ณ : dora start/dora up ๋“ฑ CLI ์‚ฌ์šฉ ์˜ˆ์‹œ๋Š” ๊ณต์‹ ๊ฐ€์ด๋“œ์™€ ์˜ˆ์ œ ๋ ˆํฌ์—์„œ ์ž์ฃผ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค.

2.4 ROS2์™€์˜ ์—ฐ๋™ โ€” ์•„ํ‚คํ…์ฒ˜ & ์‹คํ–‰ ํ๋ฆ„ (๊ตฌ์ฒด์ )

2.4.1 ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š” (ํ…์ŠคํŠธ ๋‹ค์ด์–ด๊ทธ๋žจ)

[ROS2 ๋…ธ๋“œ๋“ค] <--> [dora-ros2-bridge (ํ”„๋กœ์„ธ์Šค)] <--> [Dora ๋Ÿฐํƒ€์ž„ / dataflow graph]
                                                -> [Dora ๋…ธ๋“œ: rust-heartbeat-node]
                                                -> [Dora ๋…ธ๋“œ: perception, model, recorder ...]
  • dora-ros2-bridge๋Š” ROS2 ๋ฉ”์‹œ์ง€๋ฅผ ๋Ÿฐํƒ€์ž„์— Apache Arrow struct๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , Dora ๋‚ด๋ถ€๋กœ(๋˜๋Š” ๋ฐ˜๋Œ€๋กœ) ์ „๋‹ฌํ•ด ์ค๋‹ˆ๋‹ค. ์ฆ‰ colcon์œผ๋กœ ๋นŒ๋“œํ•  ํ•„์š” ์—†์ด (๋Ÿฐํƒ€์ž„ ๋ณ€ํ™˜์œผ๋กœ) ๊ธฐ์กด ROS2 ํ† ํ”ฝ์„ Dora ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • Bridge๋ฅผ ํ†ตํ•ด ROS2์˜ ํผ๋ธ”๋ฆฌ์…”/์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„๋Š” Dora์˜ ์ž…์ถœ๋ ฅ ์ด๋ฒคํŠธ(๋ฐ์ดํ„ฐ์•„์ด๋””)์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋งคํ•‘๋ฉ๋‹ˆ๋‹ค. (์˜ˆ: /cmd_vel โ†’ Dora output โ†’ ๋‹ค๋ฅธ Dora ๋…ธ๋“œ์—์„œ ์†Œ๋น„)

2.4.2 ๊ตฌ์ฒด์  ์—ฐ๋™ ์˜ˆ์‹œ (Rust ์ชฝ ์ฝ”๋“œ ํ๋ฆ„ ์š”์•ฝ)

  • Rust Dora ๋…ธ๋“œ์—์„œ ROS2 ๋ช…๋ น์„ ํผ๋ธ”๋ฆฌ์‹œํ•˜๋ ค๋ฉด dora-ros2-bridge ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ROS2 ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (docs ์˜ˆ์‹œ ์ฐธ์กฐ). ๋ฌธ์„œ์— ์žˆ๋Š” ์˜ˆ์‹œ(ํ† ์ด-์˜ˆ์ œ: turtlesim ์ œ์–ด)๋Š” ๋‹ค์Œ ํ๋ฆ„์ž…๋‹ˆ๋‹ค: ros2_client::Context ์ƒ์„ฑ โ†’ new_node() โ†’ create_topic() & create_publisher() โ†’ Dora ์ด๋ฒคํŠธ ๋ฃจํ”„์—์„œ ์กฐ๊ฑด์— ๋”ฐ๋ผ publisher.publish(msg) ํ˜ธ์ถœ.

(๋ฌธ์„œ์— ์ œ์‹œ๋œ Rust ์˜ˆ์‹œ ์š”์•ฝ)

  • ROS2 ์ชฝ์—์„œ create_publisher<Twist> ๋กœ ํ† ํ”ฝ์„ ๋งŒ๋“ค๊ณ , Dora ๋…ธ๋“œ์˜ ์ด๋ฒคํŠธ(์˜ˆ: โ€œtickโ€ input)๋ฅผ ๋ฐ›์•„ publisher.publish(twist) ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค. ๋”๋ถˆ์–ด ROS2์—์„œ ๋“ค์–ด์˜ค๋Š” ๋ฉ”์‹œ์ง€๋Š” merge_external๋กœ Dora ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์— ํ•ฉ์ณ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2.4.3 Python ์˜ˆ์‹œ(๊ฐ„๋‹จ) โ€” ๋ฌธ์„œ์—์„œ ๊ฐ€์ ธ์˜จ ํ๋ฆ„

import dora
from dora import Node

ros2_context = dora.Ros2Context()
ros2_node = ros2_context.new_node("turtle_teleop", "/ros2_demo", dora.Ros2NodeOptions(rosout=True))
turtle_twist_topic = ros2_node.create_topic("/turtle1/cmd_vel", "geometry_msgs/Twist", topic_qos)
twist_writer = ros2_node.create_publisher(turtle_twist_topic)
pose_reader = ros2_node.create_subscription("/turtle1/pose", "turtlesim/Pose", topic_qos)

dora_node = Node()
dora_node.merge_external_events(pose_reader)

for event in dora_node:
    # Dora ์ด๋ฒคํŠธ/์™ธ๋ถ€(ROS2) ์ด๋ฒคํŠธ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
    ...

(๋” ์ž์„ธํ•œ ์˜ˆ์‹œ๋Š” ๊ณต์‹ ๊ฐ€์ด๋“œ์˜ Bridging ROS2 ํŽ˜์ด์ง€์˜ Python / Rust ์˜ˆ์‹œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”).

2.5 ๋ฐฐํฌ/์‹คํ–‰ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (์‹ค๋ฌด ํŒ)

  1. ROS2 ๋ฉ”์‹œ์ง€ ํƒ€์ž… ์ ‘๊ทผ: ros2-bridge๊ฐ€ ROS2 ํƒ€์ž…์„ ๋Ÿฐํƒ€์ž„ ๋ณ€ํ™˜ํ•˜๋ ค๋ฉด ROS2 ํ™˜๊ฒฝ(source /opt/ros/<distro>/setup.bash ๋˜๋Š” ์›Œํฌ์ŠคํŽ˜์ด์Šค) ์ด ์‹คํ–‰ ํ™˜๊ฒฝ์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (bridge๊ฐ€ ๋ฉ”์‹œ์ง€ ์ •๋ณด๋ฅผ ์ฐพ์•„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค).
  2. dora-cli ์‚ฌ์šฉ: dora ๋ฐ๋ชฌ์œผ๋กœ dataflow๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด ๋…ธ๋“œ๋ณ„ ํ™˜๊ฒฝ๋ณ€์ˆ˜/์†Œ์ผ“์ด ์ž๋™์œผ๋กœ ์„ค์ •๋˜๋ฏ€๋กœ DoraNode::init_from_env() ํ˜ธ์ถœ์ด ์ •์ƒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„œ๋Š” dora-cli ๋ฌธ์„œ์˜ Getting Started ์ ˆ์ฐจ๋ฅผ ๋”ฐ๋ฅด์„ธ์š”.
  3. ๋ฉ”์‹œ์ง€ ํฌ๋งท: Dora ๋‚ด๋ถ€/๋ธŒ๋ฆฌ์ง€ ์‚ฌ์ด์—์„œ๋Š” Arrow ํฌ๋งท์„ ์“ฐ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ(์ œ๋กœ-์นดํ”ผ ์ด์ ). ROS2 ๋ธŒ๋ฆฟ์ง€๊ฐ€ Arrow struct๋กœ ๋ณ€ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
  4. ๋””๋ฒ„๊น…: FOSDEM/๋ฐœํ‘œ ์ž๋ฃŒ ๋ฐ GitHub ์ด์Šˆยท๋ฌธ์„œ์— ์žˆ๋Š” ์˜ˆ์ œ(ํ† ์ด ์˜ˆ์ œ: turtlesim ๋“ฑ)๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•ด ๋ณด๋ฉด ์„ค์ •ยท๋นŒ๋“œ ๋ฌธ์ œ๋ฅผ ๋น ๋ฅด๊ฒŒ ์žก์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ๋ฌธ์„œยท๋ ˆํผ๋Ÿฐ์Šค (์ง์ ‘ ์ฝ์–ด๋ณผ ๊ณณ)

  • dora Node API (Rust docs) โ€” DoraNode, send_output_raw, init_from_env() ์„ค๋ช….
  • Getting started (dora ๊ณต์‹ ๋ฌธ์„œ) โ€” ์›Œํฌ์ŠคํŽ˜์ด์Šค/๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์‹คํ–‰ ๊ฐ€์ด๋“œ.
  • Bridging ROS2 (๊ฐ€์ด๋“œ ํŽ˜์ด์ง€, Python/Rust ์˜ˆ์‹œ ํฌํ•จ) โ€” ros2-bridge ๊ฐœ๋…๊ณผ ์ฝ”๋“œ ์˜ˆ์‹œ(ํ† ์ด ์˜ˆ์ œ ํฌํ•จ). (ROS2 ์—ฐ๋™์˜ ํ•ต์‹ฌ ์˜ˆ์‹œ๊ฐ€ ์ด ํŽ˜์ด์ง€์— ์žˆ์Œ).
  • dora GitHub (์ฝ”๋“œยท์˜ˆ์ œยท๋ฆด๋ฆฌ์Šค).
  • Dataflow ์ŠคํŽ™(๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ YAML).

3 3๏ธโƒฃ Advanced

(1) ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ Rust Dora ๋…ธ๋“œ ํ…œํ”Œ๋ฆฟ, (2) ROS2 โ‡„ Dora ์—ฐ๋™(๋ธŒ๋ฆฟ์ง€) ์™„์ „ ์˜ˆ์ œ, (3) ์ปจํ…Œ์ด๋„ˆํ™”(Docker + docker-compose) ๋ฅผ ํ•œ ๋ฒˆ์— ์ •๋ฆฌํ•ด์„œ ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๋ฐ”๋กœ ๋ณต์‚ฌยท๋ถ™์—ฌ๋„ฃ์–ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŒŒ์ผ๋“ค(๋ชจ๋“  ์ฝ”๋“œ์™€ Docker ๊ตฌ์„ฑ), ๋นŒ๋“œ/์‹คํ–‰ ๋ช…๋ น, ์šด์˜ ํŒ๊ณผ ์ฃผ์˜์‚ฌํ•ญ์„ ์ •๋ฆฌํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•ต์‹ฌ ๊ทผ๊ฑฐ(๋ฌธ์„œ)๋Š” ๋ฌธ์žฅ ๋์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๋ฌธ์„œ ์ฐธ์กฐ:

  • Dora Rust ๋…ธ๋“œ API (DoraNode::init_from_env, send_output_raw) ๋ฌธ์„œ.
  • Dora ๊ณต์‹ ๊ฐ€์ด๋“œ(๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ŠคํŽ™, Getting started, Bridging ROS2).
  • Dora ๋ฆด๋ฆฌ์Šค/์„ค์น˜(๋ฐ”์ด๋„ˆ๋ฆฌ ๋‹ค์šด๋กœ๋“œ ์•ˆ๋‚ด).
  • Dora ROS2 bridge ๊ด€๋ จ ์„ค๋ช…(๋ธŒ๋ฆฌ์ง€๊ฐ€ ROS2 ๋ฉ”์‹œ์ง€ ํƒ€์ž…์„ ๋Ÿฐํƒ€์ž„์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ์‹).
  • ROS2 ๊ณต์‹ Docker ๊ฐ€์ด๋“œ(์ปจํ…Œ์ด๋„ˆ์—์„œ ROS2 ์‹คํ–‰ ๊ด€๋ จ).

3.1 ์š”์•ฝ

  1. Rust๋กœ ๋งŒ๋“  heartbeat Dora ๋…ธ๋“œ(์ œ๋กœ์นดํ”ผ ์ „์†ก)๋ฅผ ํฌํ•จํ•œ ์˜ˆ์ œ ์›Œํฌ์ŠคํŽ˜์ด์Šค.
  2. ROS2(ํŒŒ์ด์ฌ) ํผ๋ธ”๋ฆฌ์…”/์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ์˜ˆ์ œ + Dora ์ชฝ dataflow ์„ค์ •์œผ๋กœ ๋ธŒ๋ฆฌ์ง€ํ•˜๋Š” ๊ตฌ์„ฑ ์˜ˆ์‹œ(์„ค์ •ยท์„ค๋ช… ํฌํ•จ).
  3. Dockerfile๋“ค ๋ฐ docker-compose.yml๋กœ ROS2, dora(coordinator), rust node๋ฅผ ์ปจํ…Œ์ด๋„ˆ๋กœ ๋„์šฐ๋Š” ํ…œํ”Œ๋ฆฟ(ํ˜ธ์ŠคํŠธ ๋„คํŠธ์›Œํฌ ๊ถŒ์žฅ).

3.2 A. ์™„์ „ํ•œ โ€œ๊ฐ„๋‹จํ•œ Dora ๋…ธ๋“œ (Rust) ํ…œํ”Œ๋ฆฟโ€ (์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์˜ˆ์ œ)

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ: my_dora_project/

my_dora_project/
โ”œโ”€ dora-workspace/                # optional: Dora workspace / graph ํŒŒ์ผ ์ €์žฅ์†Œ
โ”‚  โ””โ”€ graphs/
โ”‚     โ””โ”€ dataflow.yml
โ”œโ”€ rust-heartbeat-node/            # Rust node
โ”‚  โ”œโ”€ Cargo.toml
โ”‚  โ””โ”€ src/
โ”‚     โ””โ”€ main.rs
โ”œโ”€ docker/                         # Dockerfile ์˜ˆ์‹œ(์•„๋ž˜ C ํ•ญ๋ชฉ๊ณผ ์ค‘๋ณต ๊ฐ€๋Šฅ)
โ””โ”€ ros2_py_pkg/                    # ROS2 Python package (ํ† ์ด ํผ๋ธ”๋ฆฌ์…”/์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„)
   โ”œโ”€ package.xml
   โ”œโ”€ setup.py
   โ”œโ”€ resource/
   โ””โ”€ ros2_py_pkg/
       โ”œโ”€ __init__.py
       โ”œโ”€ talker.py
       โ””โ”€ listener.py

3.2.1 Rust ๋…ธ๋“œ(์ฝ”๋“œ) โ€” rust-heartbeat-node

rust-heartbeat-node/Cargo.toml

[package]
name = "rust_heartbeat_node"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
futures = "0.3"
dora-node-api = "0.3"   # ์‹ค์ œ ๋ฒ„์ „์€ crates.io/docs.rs ํ™•์ธ ๊ถŒ์žฅ
dora-core = "0.3"

(์ฐธ๊ณ : dora-node-api์™€ dora-core ๋ฒ„์ „์€ ๋ฌธ์„œ/ํฌ๋ ˆ์ดํŠธ์—์„œ ์ตœ์‹  ๋ฒ„์ „ ํ™•์ธํ•˜์„ธ์š”).

rust-heartbeat-node/src/main.rs

use anyhow::Result;
use std::time::Duration;
use tokio::time::sleep;
use futures::StreamExt;

use dora_node_api::{DoraNode, MetadataParameters};
use dora_core::config::DataId;

#[tokio::main]
async fn main() -> Result<()> {
    // Dora ๋ฐ๋ชฌ์ด ์„ค์ •ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋…ธ๋“œ ์ดˆ๊ธฐํ™” (์ผ๋ฐ˜์ ์œผ๋กœ dora๊ฐ€ ๋…ธ๋“œ๋ฅผ ์Šคํฐ)
    let (mut node, mut events) = DoraNode::init_from_env()
        .expect("failed to init Dora node from env");

    // dataflow.yml์—์„œ ์ •์˜ํ•œ output id์™€ ์ผ์น˜์‹œํ‚ต๋‹ˆ๋‹ค.
    let output_id = DataId::from("heartbeat".to_owned());

    let mut counter: u64 = 0;

    loop {
        // ์ „์†กํ•  ๋ฐ”์ดํŠธ(์˜ˆ: u64 ๋ฆฌํ‹€์—”๋””์–ธ)
        let bytes = counter.to_le_bytes();

        // zero-copy ์ „์†ก: ํด๋กœ์ €๋กœ ์ œ๊ณต๋œ ๋ฒ„ํผ์— ์ง์ ‘ ์ฑ„์›Œ ๋ณด๋ƒ„
        node.send_output_raw(
            output_id.clone(),
            MetadataParameters::default(),
            bytes.len(),
            |out_buf| {
                out_buf.copy_from_slice(&bytes);
            }
        )?;

        println!("Sent heartbeat {}", counter);
        counter += 1;

        // (์„ ํƒ) ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ(์˜ˆ: ์ž…๋ ฅ, control ์ด๋ฒคํŠธ)
        // ์—ฌ๊ธฐ์„œ๋Š” ์งง๊ฒŒ ๋“œ๋ ˆ์ธ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.
        while let Ok(Some(evt)) = tokio::time::timeout(Duration::from_millis(0), events.next()).await {
            println!("Dora event: {:?}", evt);
        }

        sleep(Duration::from_millis(500)).await;
    }
}

3.2.1.1 ๋นŒ๋“œ/๋กœ์ปฌ ์‹คํ–‰(๊ฐœ๋ฐœ์ž ๋ชจ๋“œ)

  1. Rust ๋นŒ๋“œ:
cd rust-heartbeat-node
cargo build --release
# ์ƒ์„ฑ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ: target/release/rust_heartbeat_node
  1. Dora ๋Ÿฐํƒ€์ž„์œผ๋กœ ์‹คํ–‰ํ•˜๋ ค๋ฉด dataflow.yml๋กœ dora๊ฐ€ ๋…ธ๋“œ๋ฅผ spawn ํ•˜๋„๋ก ์‹คํ–‰(์•„๋ž˜ dataflow ์˜ˆ์‹œ ์ฐธ์กฐ). DoraNode::init_from_env()๋Š” dora๊ฐ€ ์„ค์ •ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.

3.2.2 ๊ฐ„๋‹จํ•œ dataflow.yml (dora-workspace/graphs/dataflow.yml)

์ด YAML์€ Dora์˜ ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์ŠคํŽ™์„ ๊ฐ„๋‹จํžˆ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ์ •๋ฆฌํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์ŠคํŽ™ ํ•„๋“œ๋Š” ๊ณต์‹ ๋ฌธ์„œ์˜ Dataflow Configuration์„ ์ฐธ๊ณ ํ•ด ํ™•์žฅ/์กฐ์ •ํ•˜์„ธ์š”.

dataflow:
  nodes:
    - id: rust_heartbeat
      path: "./rust-heartbeat-node/target/release/rust_heartbeat_node"
      args: []
      outputs:
        - id: heartbeat
          format: bytes
  • path๋Š” dora๊ฐ€ ์‹คํ–‰(์Šคํฐ)ํ•  ๋…ธ๋“œ ๋ฐ”์ด๋„ˆ๋ฆฌ์˜ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. (์ ˆ๋Œ€๊ฒฝ๋กœ/์ปจํ…Œ์ด๋„ˆ ๋‚ด ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค).

3.3 B. ROS2์™€ ์—ฐ๋™ํ•˜๋Š” ๊ตฌ์ฒด์  ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„(๋ธŒ๋ฆฟ์ง€ ์˜ˆ์‹œ ํฌํ•จ)

์•„๋ž˜๋Š” ํ† ์ด ์‹œ๋‚˜๋ฆฌ์˜ค์ž…๋‹ˆ๋‹ค:

  • ROS2 ์ชฝ: ros2_py_pkg ๋ผ๋Š” ํŒŒ์ด์ฌ ํŒจํ‚ค์ง€์— talker.py(ํผ๋ธ”๋ฆฌ์…”, /cmd_vel ๋˜๋Š” ํ† ํ”ฝ ์ด๋ฆ„ ์˜ˆ์‹œ)์™€ listener.py(์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„)๋ฅผ ๋‘ก๋‹ˆ๋‹ค.
  • Dora ์ชฝ: rust_heartbeat๊ฐ€ ๋ณด๋‚ด๋Š” heartbeat ๋ฐ์ดํ„ฐ๋ฅผ Dora ๋‚ด๋ถ€์—์„œ ์†Œ๋น„ํ•˜๊ฑฐ๋‚˜, dora-ros2-bridge๋ฅผ ํ†ตํ•ด ROS2 ํ† ํ”ฝ์œผ๋กœ publish ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Bridge๋Š” Dora ๋Ÿฐํƒ€์ž„์˜ ํ™•์žฅ(libraries/extensions/ros2-bridge)์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ROS2 ๋ฉ”์‹œ์ง€ ํƒ€์ž…์„ ๋Ÿฐํƒ€์ž„์—์„œ ์ƒ์„ฑ/๋งคํ•‘ํ•ด์ค๋‹ˆ๋‹ค.

3.3.1 ROS2 Python ํผ๋ธ”๋ฆฌ์…”/์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ (ํ† ์ด)

ros2_py_pkg/package.xml (๊ฐ„๋‹จ)

<?xml version="1.0"?>
<package format="3">
  <name>ros2_py_pkg</name>
  <version>0.0.0</version>
  <description>toy ros2 python pub/sub for dora example</description>
  <maintainer email="you@example.com">you</maintainer>
  <license>Apache-2.0</license>

  <depend>rclpy</depend>
  <depend>geometry_msgs</depend>
</package>

ros2_py_pkg/setup.py

from setuptools import setup

package_name = 'ros2_py_pkg'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='you',
    maintainer_email='you@example.com',
    description='ROS2 python toy pub/sub',
    license='Apache-2.0',
    entry_points={
        'console_scripts': [
            'talker = ros2_py_pkg.talker:main',
            'listener = ros2_py_pkg.listener:main',
        ],
    },
)

ros2_py_pkg/ros2_py_pkg/talker.py (ํผ๋ธ”๋ฆฌ์…”)

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

class Talker(Node):
    def __init__(self):
        super().__init__('dora_talker')
        self.pub = self.create_publisher(Twist, '/dora/cmd_vel', 10)
        self.timer = self.create_timer(0.5, self.on_timer)
        self.cnt = 0

    def on_timer(self):
        msg = Twist()
        msg.linear.x = 0.1 * (self.cnt % 10)
        msg.angular.z = 0.01 * (self.cnt % 20)
        self.pub.publish(msg)
        self.get_logger().info(f'Published cmd_vel: linear.x={msg.linear.x:.2f}')
        self.cnt += 1

def main():
    rclpy.init()
    node = Talker()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    rclpy.shutdown()

ros2_py_pkg/ros2_py_pkg/listener.py (์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„)

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

class Listener(Node):
    def __init__(self):
        super().__init__('dora_listener')
        self.sub = self.create_subscription(Twist, '/dora/cmd_vel', self.cb, 10)

    def cb(self, msg):
        self.get_logger().info(f'Recv cmd_vel: linear.x={msg.linear.x:.3f}, angular.z={msg.angular.z:.3f}')

def main():
    rclpy.init()
    node = Listener()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    rclpy.shutdown()

์œ„ ROS2 ์˜ˆ์ œ๋Š” ROS2 ๊ณต์‹ ํŠœํ† ๋ฆฌ์–ผ(ํ† ์ด ์˜ˆ์ œ, turtlesim ๋“ฑ)๊ณผ ๋™์ผํ•œ ๊ธฐ๋ณธ ํŒจํ„ด์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆํ™”/์‹คํ–‰์€ ROS2 Docker ๊ฐ€์ด๋“œ ๊ถŒ์žฅ์‚ฌํ•ญ์„ ๋”ฐ๋ฅด์„ธ์š”. ([ROS Documentation][11])


3.3.2 Dora โ†”๏ธŽ ROS2 ๋ธŒ๋ฆฌ์ง€๋ฅผ ํ†ตํ•œ ๋งคํ•‘(์„ค๊ณ„ & ์˜ˆ์‹œ)

Dora์˜ Bridging ๊ฐœ๋… ์š”์•ฝ:

  • Dora์˜ ROS2 bridge๋Š” ROS2 ๋ฉ”์‹œ์ง€ ํƒ€์ž…์„ ๋Ÿฐํƒ€์ž„์—์„œ ์ฐพ์•„ Dora API๋กœ ์ด์šฉ ๊ฐ€๋Šฅํ•œ ํ•จ์ˆ˜(create_topic_<TYPE> ๋“ฑ)๋ฅผ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ Dora ์ชฝ์—์„œ ROS2 ํ† ํ”ฝ์„ ํผ๋ธ”๋ฆฌ์‹œ/๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

3.3.2.1 ์˜ˆ์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค

  • Dora ๋‚ด๋ถ€์—์„œ heartbeat ๋ฐ์ดํ„ฐ๋ฅผ ROS2 std_msgs/Int64 ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด /dora/heartbeat ํ† ํ”ฝ์œผ๋กœ publish.
  • ROS2 talker๋Š” Dora๋ฅผ ๋ชจ๋ฅด๋Š” ์ƒํƒœ๋กœ ๋™์ž‘(๋˜๋Š” ๋ฐ˜๋Œ€๋กœ).

Dora ์ชฝ์—์„œ ROS2 publish๋ฅผ ํ•˜๋ ค๋ฉด ๋ณดํ†ต dora-ros2-bridge extension์„ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜, Dora ๋Ÿฐํƒ€์ž„์„ ๋นŒ๋“œํ•  ๋•Œ bridge ํ™•์žฅ์„ ํฌํ•จ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋ฌธ์„œ/๋นŒ๋“œ ๊ฐ€์ด๋“œ ์ฐธ์กฐ). ([GitHub][12])

3.3.2.1.1 ๋Œ€๋žต์  ํ๋ฆ„(์˜์‚ฌ ์ฝ”๋“œ)
  • Dora ๋…ธ๋“œ(์˜ˆ: Rust ๋˜๋Š” Python)์—์„œ ros2-contex๋ฅผ ์ดˆ๊ธฐํ™” โ†’ new_node(...) โ†’ create_publisher_std_msgs_int64("/dora/heartbeat") โ†’ Dora event loop์—์„œ publish.

Dora C++/Rust API ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด ๋ธŒ๋ฆฌ์ง€ ์ชฝ์— create_topic_<TYPE> ํ˜น์€ ์ž๋™ ์ƒ์„ฑ๋œ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด topic์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋ฌธ์„œ์— ๋‚˜์™€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ๋Š” ๊ฐœ๋…์„ ์œ„ํ•œ ์˜์‚ฌ ์ฝ”๋“œ์ด๋ฉฐ, ์‹ค์ œ ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” ๋ธŒ๋ฆฌ์ง€ ๋ฒ„์ „/๋ฌธ์„œ ํ™•์ธ ํ›„ ์‚ฌ์šฉํ•˜์„ธ์š”.


3.4 C. ์ปจํ…Œ์ด๋„ˆํ™” (Docker + docker-compose) โ€” ROS2 + Dora + Rust node

๊ถŒ์žฅ: ROS2 (DDS) ๋ฐ ํ•˜๋“œ์›จ์–ด ์ ‘๊ทผ์„ฑ ๋•Œ๋ฌธ์— network: host ๋ชจ๋“œ๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์ด์œ ์™€ ๊ถŒ์žฅ ๋ฐฉ๋ฒ•์€ ROS2 Docker ๋ฌธ์„œ ์ฐธ์กฐ.

ํ”„๋กœ์ ํŠธ์— ํฌํ•จํ•  ์˜ˆ์‹œ docker/ ํŒŒ์ผ๋“ค(๊ฐ„๋‹จ ๋ฒ„์ „):

3.4.1 docker/Dockerfile.dora โ€” dora ๋ฐ๋ชฌ/CLI (๊ฐ„๋‹จ: release ๋ฐ”์ด๋„ˆ๋ฆฌ ์‚ฌ์šฉ)

# dora coordinator image (lightweight)
FROM ubuntu:22.04

ARG DORA_VERSION="v0.3.0"   # ์‹ค์ œ ์ตœ์‹  ๋ฆด๋ฆฌ์Šค ํƒœ๊ทธ๋กœ ๋ฐ”๊พธ์„ธ์š”
WORKDIR /opt/dora

# ๊ธฐ๋ณธ ํˆด ์„ค์น˜
RUN apt-get update && apt-get install -y curl unzip ca-certificates && rm -rf /var/lib/apt/lists/*

# GitHub release์—์„œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋‹ค์šด๋กœ๋“œ(๋ฌธ์„œ ๊ถŒ์žฅ ๋ฐฉ์‹)
# ์‹ค์ œ ๋ฆด๋ฆฌ์Šค ํŒŒ์ผ๋ช…์€ GitHub Releases ํŽ˜์ด์ง€์—์„œ ํ™•์ธํ•˜์„ธ์š”.
RUN curl -L -o /tmp/dora.zip "https://github.com/dora-rs/dora/releases/download/${DORA_VERSION}/dora-${DORA_VERSION}-x86_64-Linux.zip" \
 && unzip /tmp/dora.zip -d /opt/dora \
 && rm /tmp/dora.zip

ENV PATH="/opt/dora:${PATH}"

# ๊ธฐ๋ณธ ํฌํŠธ/ํŒŒ์ผ ์‹œ์Šคํ…œ ๋งˆ์šดํŠธ๋Š” docker-compose์—์„œ ์„ค์ •
CMD ["dora", "--help"]
  • dora ์„ค์น˜ ์•ˆ๋‚ด๋Š” ๊ณต์‹ ๋ฌธ์„œ์—์„œ โ€œRelease zip downloadโ€ ๋ฐฉ์‹์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. (์œ„ Dockerfile์€ ๊ทธ ๊ถŒ์žฅ ๋ฐฉ์‹์„ ์ž๋™ํ™”).

์ฃผ์˜: DORA_VERSION์€ ์‹ค์ œ Release ํƒœ๊ทธ(์˜ˆ: v0.3.0)๋กœ ๋ฐ”๊พธ์„ธ์š”. GitHub Releases ํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค.

3.4.2 docker/Dockerfile.ros2 โ€” ROS2 (humble) ์ปจํ…Œ์ด๋„ˆ

FROM osrf/ros:humble-ros-core

# ๊ธฐ๋ณธ ํˆด
RUN apt-get update && apt-get install -y python3-pip python3-colcon-common-extensions && rm -rf /var/lib/apt/lists/*

# (์„ ํƒ) ์‚ฌ์šฉ์ž ํŒจํ‚ค์ง€ ๋ณต์‚ฌ/๋นŒ๋“œ ์ง€์ ์€ docker-compose์—์„œ volume์œผ๋กœ ๋งˆ์šดํŠธํ•ด ์‚ฌ์šฉ ๊ถŒ์žฅ
WORKDIR /work

CMD ["/bin/bash"]
  • ROS2 ๊ณต์‹ Docker ์‚ฌ์šฉ ๊ฐ€์ด๋“œ๋Š” ์ปจํ…Œ์ด๋„ˆ์—์„œ ROS2๋ฅผ ํ…Œ์ŠคํŠธ/๊ฐœ๋ฐœํ•  ๋•Œ host ๋„คํŠธ์›Œํ‚น์„ ๊ถŒ์žฅํ•˜๊ฑฐ๋‚˜ ํ™˜๊ฒฝ์„ ๋งž์ถฐ์ฃผ๋Š” ์‚ฌ๋ก€๋ฅผ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.

3.4.3 docker/Dockerfile.rustnode โ€” Rust ๋…ธ๋“œ๋ฅผ ๋นŒ๋“œํ•ด ๋Ÿฐํƒ€์ž„ ๋ฐ”์ด๋„ˆ๋ฆฌ๋งŒ ๋‹ด๋Š” ๋‹ค๋‹จ๊ณ„ ๋นŒ๋“œ

# builder
FROM rust:1.73 as builder
WORKDIR /usr/src/app
COPY ./rust-heartbeat-node/ ./rust-heartbeat-node/
WORKDIR /usr/src/app/rust-heartbeat-node
RUN cargo build --release

# runtime
FROM debian:bookworm-slim
COPY --from=builder /usr/src/app/rust-heartbeat-node/target/release/rust_heartbeat_node /usr/local/bin/rust_heartbeat_node
WORKDIR /app
CMD ["/usr/local/bin/rust_heartbeat_node"]

3.4.4 docker-compose.yml (๊ฐ„๋‹จ ํ…œํ”Œ๋ฆฟ)

version: '3.8'
services:
  dora:
    build:
      context: ./docker
      dockerfile: Dockerfile.dora
    container_name: dora_coordinator
    network_mode: "host"
    volumes:
      - ./dora-workspace:/work/dora-workspace
    environment:
      - TZ=Asia/Seoul

  ros2:
    build:
      context: ./docker
      dockerfile: Dockerfile.ros2
    container_name: ros2_core
    network_mode: "host"
    volumes:
      - ./ros2_py_pkg:/work/ros2_py_pkg
    working_dir: /work
    command: /bin/bash -c "cd ros2_py_pkg && /bin/bash -lc 'pip3 install -e . && ros2 run ros2_py_pkg talker'"

  rustnode:
    build:
      context: .
      dockerfile: docker/Dockerfile.rustnode
    container_name: rust_heartbeat_node
    network_mode: "host"
    depends_on:
      - dora
  • network_mode: "host": ROS2 DDS์™€ dora์˜ ๋„คํŠธ์›Œํฌ/IPC๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. (Docker์™€ DDS๋Š” ๋„คํŠธ์›Œํฌ/ํฌํŠธ ๋งคํ•‘/๋ฉ€ํ‹ฐํ˜ธ์ŠคํŠธ์—์„œ ๋ณต์žกํ•จ์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์–ด ํ˜ธ์ŠคํŠธ ๋„คํŠธ์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ„๋‹จํ•จ).
  • ๋ณผ๋ฅจ ๋งˆ์šดํŠธ๋กœ ROS2 ํŒจํ‚ค์ง€ ์†Œ์Šค์™€ dora ๊ทธ๋ž˜ํ”„๋ฅผ ๊ณต์œ ํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

3.5 ์ „์ฒด ์‹คํ–‰ ์ ˆ์ฐจ

  1. ์ฝ”๋“œ/ํŒŒ์ผ ๋ณต์‚ฌ: ์œ„ ํŒŒ์ผ๋“ค์„ ๋กœ์ปฌ my_dora_project/ ๊ตฌ์กฐ๋กœ ๋ฐฐ์น˜.
  2. Rust ๋นŒ๋“œ & ๋ฐ”์ด๋„ˆ๋ฆฌ ํ™•์ธ(๋กœ์ปฌ์—์„œ ๋จผ์ € ํ™•์ธ ๊ถŒ์žฅ):
cd my_dora_project/rust-heartbeat-node
cargo build --release
./target/release/rust_heartbeat_node   # ๋™์ž‘ ํ™•์ธ (๋‹จ๋…์‹คํ–‰์€ Dora env๊ฐ€ ์—†์œผ๋ฉด init_from_env ์‹คํŒจ ๊ฐ€๋Šฅ)

์ฃผ์˜: DoraNode::init_from_env()๋Š” dora ๋Ÿฐํƒ€์ž„์ด ์„ค์ •ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ์—์„œ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด(๋ฐ๋ชฌ ์—†์ด) init_from_env ๋Œ€์‹  DoraNode::init_dummy() ๊ฐ™์€ ๊ฐœ๋ฐœ ํ—ฌํผ๊ฐ€ ๋ฌธ์„œ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜, ์ˆ˜๋™์œผ๋กœ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๊ฐœ๋ฐœ ์‹œ์ ์˜ ๋ฌธ์„œ ์ฐธ๊ณ ).

  1. Docker ๋นŒ๋“œ/์‹คํ–‰ (ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์—์„œ):
# docker-compose ๋นŒ๋“œ + ์‹คํ–‰
docker compose build
sudo docker compose up
  1. dora graph ์‹คํ–‰: (์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ ๋˜๋Š” ํ˜ธ์ŠคํŠธ์—์„œ)
# ์˜ˆ: dora ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ์ปจํ…Œ์ด๋„ˆ์— ์žˆ๊ณ , graphs/dataflow.yml์„ ๋งˆ์šดํŠธํ–ˆ์œผ๋ฉด
# ์ปจํ…Œ์ด๋„ˆ์— ์ ‘์†ํ•ด์„œ
docker exec -it dora_coordinator /bin/bash
# inside container
dora start /work/dora-workspace/graphs/dataflow.yml --attach

dora ์„ค์น˜/๋ฆด๋ฆฌ์Šค ๋‹ค์šด๋กœ๋“œ/CLI ์‚ฌ์šฉ๋ฒ•์€ ๊ณต์‹ ์„ค์น˜ ๊ฐ€์ด๋“œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

  1. ROS2 ํผ๋ธ”๋ฆฌ์…”/์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ๋™์ž‘ ํ™•์ธ:
  • ros2 ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ๊ฐ€ talker๋ฅผ ์‹œ์ž‘ํ•˜๋„๋ก docker-compose์— ์ ์–ด๋†จ๋‹ค๋ฉด (ํ˜น์€ ํ˜ธ์ŠคํŠธ์—์„œ ์ง์ ‘):
# host or inside ros2 container
# ํŒจํ‚ค์ง€ ์„ค์น˜
cd ros2_py_pkg
pip3 install -e .
# ํ„ฐ๋ฏธ๋„1: talker
ros2 run ros2_py_pkg talker
# ํ„ฐ๋ฏธ๋„2: listener
ros2 run ros2_py_pkg listener

3.6 ๊ตฌํ˜„/์šด์˜ ํŒ & ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

  • Dora ๋ธŒ๋ฆฌ์ง€ ์‚ฌ์šฉ: Dora ROS2 bridge๋Š” Dora ์†Œ์Šค์˜ libraries/extensions/ros2-bridge์— ํ†ตํ•ฉ๋˜์–ด ๊ฐœ๋ฐœ/๋ฐฐํฌ๋˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋นŒ๋“œ/์‚ฌ์šฉ ์‹œ ๋ธŒ๋ฆฌ์ง€ ํ™•์žฅ์ด ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ([GitHub][12])
  • Zero-copy ์žฅ์ : send_output_raw ๊ฐ™์€ API๋Š” ๋ฒ„ํผ ํด๋กœ์ €๋กœ ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„์›Œ ์ „์†ก(์ œ๋กœ์นดํ”ผ)ํ•˜๋ฏ€๋กœ ์ €์ง€์—ฐ/๊ณ ์„ฑ๋Šฅ ํŒŒ์ดํ”„๋ผ์ธ์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ™œ์šฉํ•ด ๋Œ€์šฉ๋Ÿ‰ ์„ผ์„œ(์ด๋ฏธ์ง€, ๋ผ์ด๋‹ค) ์ „์†ก ์‹œ ๋ณต์‚ฌ ๋น„์šฉ์„ ์ค„์ด์„ธ์š”.
  • ROS2 + Docker: DDS ๋„ค์ดํ‹ฐ๋ธŒ ๋™์ž‘์„ ์œ„ํ•ด network_mode: host๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ฉ€ํ‹ฐ ํ˜ธ์ŠคํŠธ/๋ณด์•ˆ์ด์Šˆ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ถ”๊ฐ€ ๋„คํŠธ์›Œํฌ/๋‘ ์ปจํ…Œ์ด๋„ˆ ๊ฐ„ bridge ์„ค์ •ยทROS2์˜ RTPS ์„ค์ •์„ ์กฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ/๋””๋ฐ”์ด์Šค: ์‹ค ํ•˜๋“œ์›จ์–ด(์˜ˆ: GPU, USB ์นด๋ฉ”๋ผ ๋“ฑ)๋ฅผ ์ปจํ…Œ์ด๋„ˆ์— ๋ถ™์ผ ๊ฒฝ์šฐ --device ์˜ต์…˜ ํ˜น์€ nvidia-docker ๋Ÿฐํƒ€์ž„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: carla ๋“ฑ dora-drives ์˜ˆ์ œ). ([Dora RS][14])

3.7 ์ถ”๊ฐ€ ์ž๋ฃŒ(๋ฐ”๋กœ ํ™•์ธํ•ด์•ผ ํ•  ๋ฌธ์„œ ๋งํฌ ์š”์•ฝ)

  • Dora Guides (Getting started, Bridging ROS2 ๋“ฑ).
  • Dora Dataflow Configuration (YAML ์ŠคํŽ™).
  • Dora Rust API docs (DoraNode, send_output_raw, init_from_env).
  • Dora GitHub (์†Œ์Šค/๋ฆด๋ฆฌ์Šค).
  • ROS2 Docker ์‹คํ–‰ ๊ฐ€์ด๋“œ.

File source

  • ZIP ์•ˆ์— ํฌํ•จ๋œ ๊ฒƒ: Rust Dora ๋…ธ๋“œ ํ…œํ”Œ๋ฆฟ, dataflow.yml, ROS2(ํŒŒ์ด์ฌ) ํผ๋ธ”๋ฆฌ์…”ยท์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„, Dockerfile๋“ค, docker-compose.yml (๋ชจ๋‘ ์ฆ‰์‹œ ๋ณต์‚ฌยท๋ถ™์—ฌ๋„ฃ์–ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ).
  • Dora ๊ด€๋ จ ๋ฒ„์ „(ํŒŒ์ผ ๋‚ด ๊ธฐ๋ณธ๊ฐ’):
    • Dockerfile์˜ DORA_VERSION์€ v0.3.13(GitHub ์ตœ์‹  ๋ฆด๋ฆฌ์Šค ๊ธฐ์ค€).
    • Cargo.toml์€ dora-node-api = โ€œ0.3.13โ€, dora-core = โ€œ0.3.13โ€์œผ๋กœ ์„ค์ •ํ•ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.
    • dora-ros2-bridge ๊ด€๋ จ ์ฝ”๋“œ๋Š” ์ฃผ์„์œผ๋กœ ๋‚จ๊ฒจ๋‘์—ˆ์œผ๋‹ˆ, ๋กœ์ปฌ์— ์„ค์น˜๋œ ๋ธŒ๋ฆฌ์ง€ ๋ฒ„์ „์— ๋งž์ถฐ ์ฃผ์„์„ ํ•ด์ œํ•˜๊ณ  ๋ฒ„์ „์„ ๋งž์ถ”์„ธ์š”(๋ธŒ๋ฆฌ์ง€์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” ๋ฒ„์ „๋ณ„๋กœ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค)

  • Dora โ€” ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€ (Introduction)
  • Dora main site (dora-rs.ai)
  • Getting started โ€” dora-rs
  • Guides โ€” dora-rs
  • Installing โ€” dora-rs (Guides โ†’ Installing)
  • Dataflow Configuration โ€” dora (docs)
  • Dataflow Configuration โ€” dora (alternate page)
  • Rust API โ€” dora (Rust API page)
  • dora_node_api โ€” docs.rs (crate docs)
  • DoraNode struct โ€” docs.rs (DoraNode API)
  • dora-node-api โ€” crates.io (package)
  • dora GitHub โ€” repository
  • dora Releases โ€” GitHub Releases
  • dora-ros2-bridge โ€” GitHub (bridge repo)
  • dora-drives โ€” installation (dora-drives docs)
  • dora-drives โ€” GitHub (repo)
  • dora-lerobot โ€” GitHub (Lerobot with dora)
  • FOSDEM โ€” โ€œModern Dataflow Framework for Roboticsโ€ (PDF slides)
  • Running ROS 2 nodes in Docker (community guide, Foxy)
  • Using turtlesim, ros2, and rqt (tutorial, Foxy)

Copyright 2024, Jung Yeon Lee