๐งฉDORA
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 ์ํคํ ์ฒ์ ๋์ ๋ฐฉ์(๊ฐ๋จํ ๊ฐ๋ )
- ๋ ธ๋(node): ์ผ์ ์ ๋ ฅ, ๋ชจ๋ธ ์ถ๋ก , ํ๋๋, ๋ก๊ทธ ๋ฑ ๊ฐ๊ฐ์ ์ฐ์ฐ ๋จ์๋ฅผ ๋ ธ๋๋ก ๊ตฌํํฉ๋๋ค. ๋ ธ๋๋ ์ ๋ ฅ์ ๋ฐ๊ณ ์ถ๋ ฅ(๋๋ ์ฌ๋ฌ ์ถ๋ ฅ)์ ๋ฐํํฉ๋๋ค.
- ์คํผ๋ ์ดํฐ(operator): ๋ ธ๋ ๋ด๋ถ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๊ฑฐ๋ ๋ผ์ฐํ ํ๋ ์์ ์ฐ์ฐ ๋จ์๋ก ๋ณผ ์ ์์ต๋๋ค.
- ๊ทธ๋ํ ์ ์:
dataflow.yml
๊ฐ์ ๊ทธ๋ํ ์ ์ ํ์ผ๋ก ๋ ธ๋ ๊ฐ ์ฐ๊ฒฐ(ํ์ดํ๋ผ์ธ)์ ์ ์ธํ๊ณ , DORA ๋ฐํ์์ด ์ด๋ฅผ ํด์ํด ๋ ธ๋ ๊ฐ ํต์ ์ ์ค์ ํฉ๋๋ค. - ๋ฐ์ดํฐ ํฌ๋งท: ์ฑ๋ฅ์ ์ํด 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 ๋ฐฐํฌ/์คํ ์ฒดํฌ๋ฆฌ์คํธ (์ค๋ฌด ํ)
- ROS2 ๋ฉ์์ง ํ์
์ ๊ทผ: ros2-bridge๊ฐ ROS2 ํ์
์ ๋ฐํ์ ๋ณํํ๋ ค๋ฉด ROS2 ํ๊ฒฝ(
source /opt/ros/<distro>/setup.bash
๋๋ ์ํฌ์คํ์ด์ค) ์ด ์คํ ํ๊ฒฝ์์ ์ ๊ทผ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค. (bridge๊ฐ ๋ฉ์์ง ์ ๋ณด๋ฅผ ์ฐพ์ ์์ฑํฉ๋๋ค). - dora-cli ์ฌ์ฉ: dora ๋ฐ๋ชฌ์ผ๋ก dataflow๋ฅผ ์์ํ๋ฉด ๋
ธ๋๋ณ ํ๊ฒฝ๋ณ์/์์ผ์ด ์๋์ผ๋ก ์ค์ ๋๋ฏ๋ก
DoraNode::init_from_env()
ํธ์ถ์ด ์ ์ ๋์ํฉ๋๋ค. ๋ก์ปฌ ํ ์คํธ๋ฅผ ์ํด์๋ dora-cli ๋ฌธ์์ Getting Started ์ ์ฐจ๋ฅผ ๋ฐ๋ฅด์ธ์. - ๋ฉ์์ง ํฌ๋งท: Dora ๋ด๋ถ/๋ธ๋ฆฌ์ง ์ฌ์ด์์๋ Arrow ํฌ๋งท์ ์ฐ๋ ๊ฒ์ ๊ถ์ฅ(์ ๋ก-์นดํผ ์ด์ ). ROS2 ๋ธ๋ฆฟ์ง๊ฐ Arrow struct๋ก ๋ณํํด์ค๋๋ค.
- ๋๋ฒ๊น : 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 ์์ฝ
- Rust๋ก ๋ง๋
heartbeat
Dora ๋ ธ๋(์ ๋ก์นดํผ ์ ์ก)๋ฅผ ํฌํจํ ์์ ์ํฌ์คํ์ด์ค. - ROS2(ํ์ด์ฌ) ํผ๋ธ๋ฆฌ์ /์๋ธ์คํฌ๋ผ์ด๋ฒ ์์ + Dora ์ชฝ dataflow ์ค์ ์ผ๋ก ๋ธ๋ฆฌ์งํ๋ ๊ตฌ์ฑ ์์(์ค์ ยท์ค๋ช ํฌํจ).
- 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 ๋น๋/๋ก์ปฌ ์คํ(๊ฐ๋ฐ์ ๋ชจ๋)
- Rust ๋น๋:
cd rust-heartbeat-node
cargo build --release
# ์์ฑ๋ ๋ฐ์ด๋๋ฆฌ: target/release/rust_heartbeat_node
- 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
๋ฐ์ดํฐ๋ฅผ ROS2std_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 ์ ์ฒด ์คํ ์ ์ฐจ
- ์ฝ๋/ํ์ผ ๋ณต์ฌ: ์ ํ์ผ๋ค์ ๋ก์ปฌ
my_dora_project/
๊ตฌ์กฐ๋ก ๋ฐฐ์น. - 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()
๊ฐ์ ๊ฐ๋ฐ ํฌํผ๊ฐ ๋ฌธ์์ ์๋์ง ํ์ธํ๊ฑฐ๋, ์๋์ผ๋ก ํ๊ฒฝ๋ณ์๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค(๊ฐ๋ฐ ์์ ์ ๋ฌธ์ ์ฐธ๊ณ ).
- Docker ๋น๋/์คํ (ํ๋ก์ ํธ ๋ฃจํธ์์):
- 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 ์ฌ์ฉ๋ฒ์ ๊ณต์ ์ค์น ๊ฐ์ด๋๋ฅผ ํ์ธํ์ธ์.
- ROS2 ํผ๋ธ๋ฆฌ์ /์๋ธ์คํฌ๋ผ์ด๋ฒ ๋์ ํ์ธ:
ros2
์๋น์ค ์ปจํ ์ด๋๊ฐtalker
๋ฅผ ์์ํ๋๋กdocker-compose
์ ์ ์ด๋จ๋ค๋ฉด (ํน์ ํธ์คํธ์์ ์ง์ ):
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 ์คํ ๊ฐ์ด๋.
- 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)