Skip to content

Commit b911cfd

Browse files
authored
feat: backend selection between ros2dds and rmw_zenoh (#80)
1 parent eb2764d commit b911cfd

File tree

30 files changed

+3142
-157
lines changed

30 files changed

+3142
-157
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# Experimental
3232

3333
- [Shared Memory (SHM)](./chapters/shm.md)
34+
- [Backend Selection](./chapters/backends.md)
3435
- [rmw_zenoh_rs](./chapters/rmw_zenoh_rs.md)
3536
- [Python Bindings](./chapters/python.md)
3637
- [Code Generation Internals](./chapters/python_codegen.md)

book/src/chapters/backends.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# Backend Selection
2+
3+
**ros-z supports multiple backend protocols for Zenoh key expression generation, enabling interoperability with different ROS 2-to-Zenoh bridges.** The backend determines how topic names are mapped to Zenoh key expressions, allowing seamless communication across different deployment architectures.
4+
5+
```admonish note
6+
Backend selection is a compile-time or runtime choice that affects how ros-z maps ROS 2 topics to Zenoh key expressions. Choose the backend that matches your bridge infrastructure for proper message routing.
7+
```
8+
9+
## Available Backends
10+
11+
ros-z provides two backend implementations:
12+
13+
| Backend | Key Expression Format | Use Case | Bridge Compatibility |
14+
|---------|----------------------|----------|---------------------|
15+
| **RmwZenoh** | `<domain_id>/<topic>/**` | Standard ROS 2 integration | `rmw_zenoh_cpp` middleware |
16+
| **Ros2Dds** | `<topic>/**` | DDS bridge compatibility | `zenoh-bridge-ros2dds` |
17+
18+
### RmwZenoh Backend (Default)
19+
20+
The RmwZenoh backend is designed for compatibility with ROS 2's official Zenoh middleware implementation.
21+
22+
**Key Expression Format:**
23+
24+
```text
25+
<domain_id>/<topic>/**
26+
```
27+
28+
**Example:**
29+
30+
```text
31+
0/chatter/** # Domain 0, topic /chatter
32+
5/robot/status/** # Domain 5, topic /robot/status
33+
```
34+
35+
**Use this backend when:**
36+
37+
- Using `rmw_zenoh_cpp` as your ROS 2 middleware
38+
- Running pure ros-z deployments
39+
- Requiring domain isolation via Zenoh
40+
41+
### Ros2Dds Backend
42+
43+
The Ros2Dds backend is designed for compatibility with `zenoh-bridge-ros2dds`, which bridges standard DDS-based ROS 2 nodes to Zenoh.
44+
45+
**Key Expression Format:**
46+
47+
```text
48+
<topic>/**
49+
```
50+
51+
**Example:**
52+
53+
```text
54+
chatter/** # Topic /chatter (no domain prefix)
55+
robot/status/** # Topic /robot/status
56+
```
57+
58+
**Use this backend when:**
59+
60+
- Bridging existing DDS-based ROS 2 systems to Zenoh
61+
- Using `zenoh-bridge-ros2dds`
62+
- Integrating with CycloneDDS or FastDDS nodes via Zenoh
63+
64+
## Specifying Backend in Code
65+
66+
### Type-Based Selection (Compile-Time)
67+
68+
Use the builder pattern with generic type parameters for compile-time backend selection:
69+
70+
```rust,ignore
71+
use ros_z::{Builder, backend::{RmwZenohBackend, Ros2DdsBackend}};
72+
use ros_z::qos::{QosProfile, QosHistory};
73+
use ros_z_msgs::std_msgs::String as RosString;
74+
use ros_z_msgs::example_interfaces::srv::AddTwoInts;
75+
use ros_z_msgs::action_tutorials_interfaces::action::Fibonacci;
76+
77+
// Create context and node
78+
let ctx = ZContextBuilder::default().build()?;
79+
let node = ctx.create_node("my_node").build()?;
80+
81+
// Publisher with RmwZenoh backend (default)
82+
let pub_rmw = node
83+
.create_pub::<RosString>("chatter")
84+
.with_backend::<RmwZenohBackend>() // Explicit backend
85+
.build()?;
86+
87+
// Subscriber with Ros2Dds backend
88+
let sub_dds = node
89+
.create_sub::<RosString>("chatter")
90+
.with_backend::<Ros2DdsBackend>() // DDS bridge compatibility
91+
.build()?;
92+
93+
// Service client with RmwZenoh backend
94+
let client = node
95+
.create_client::<AddTwoInts>("add_two_ints")
96+
.with_backend::<RmwZenohBackend>()
97+
.build()?;
98+
99+
// Service server with Ros2Dds backend
100+
let mut server = node
101+
.create_service::<AddTwoInts>("add_two_ints")
102+
.with_backend::<Ros2DdsBackend>()
103+
.build()?;
104+
105+
// Action client with RmwZenoh backend
106+
let action_client = node
107+
.create_action_client::<Fibonacci>("fibonacci")
108+
.with_backend::<RmwZenohBackend>()
109+
.build()?;
110+
111+
// Action server with Ros2Dds backend
112+
let mut action_server = node
113+
.create_action_server::<Fibonacci>("fibonacci")
114+
.with_backend::<Ros2DdsBackend>()
115+
.build()?;
116+
```
117+
118+
**Key points:**
119+
120+
- Backend is specified via generic type parameter
121+
- Default backend is `RmwZenohBackend` if not specified
122+
- Type-safe selection ensures correct key expression format
123+
- No runtime overhead - resolved at compile time
124+
125+
### Default Backend Behavior
126+
127+
If no backend is specified, ros-z uses `RmwZenohBackend`:
128+
129+
```rust,ignore
130+
// These are equivalent:
131+
let pub1 = node.create_pub::<RosString>("topic").build()?;
132+
let pub2 = node.create_pub::<RosString>("topic")
133+
.with_backend::<RmwZenohBackend>()
134+
.build()?;
135+
```
136+
137+
### Multiple Backend Features
138+
139+
When both `rmw-zenoh` and `ros2dds` feature flags are enabled in `Cargo.toml`:
140+
141+
```toml
142+
[dependencies]
143+
ros-z = { version = "0.1", features = ["rmw-zenoh", "ros2dds"] }
144+
```
145+
146+
**Default behavior**: `RmwZenohBackend` is used by default (more established, backwards compatible).
147+
148+
To use the ros2dds backend, explicitly specify it:
149+
150+
```rust,ignore
151+
let publisher = node
152+
.create_pub::<RosString>("chatter")
153+
.with_backend::<Ros2DdsBackend>() // Explicit opt-in
154+
.build()?;
155+
```
156+
157+
**Rationale**: The `rmw-zenoh` backend is more established and maintains backwards compatibility with existing ros-z deployments. The `ros2dds` backend requires explicit opt-in to ensure users are aware they're using bridge-compatible key expressions.
158+
159+
## Architecture Diagrams
160+
161+
### RmwZenoh Backend Architecture
162+
163+
```mermaid
164+
graph LR
165+
A[ros-z Node<br/>RmwZenohBackend] -->|"0/chatter/**"| B[Zenoh Router<br/>rmw_zenoh]
166+
B -->|"0/chatter/**"| C[ROS 2 Node<br/>rmw_zenoh_cpp]
167+
```
168+
169+
**Use case:** Native Zenoh-based ROS 2 deployment
170+
171+
- All nodes use rmw_zenoh or ros-z
172+
- Direct Zenoh communication
173+
- Domain isolation via key expression prefix
174+
175+
### Ros2Dds Backend Architecture
176+
177+
```mermaid
178+
graph LR
179+
A[ros-z Node<br/>Ros2DdsBackend] -->|"chatter/**"| B[zenoh-bridge-ros2dds<br/>Router + Bridge]
180+
B -->|DDS| C[ROS 2 Node<br/>CycloneDDS/FastDDS]
181+
```
182+
183+
**Use case:** Bridge existing DDS systems to Zenoh
184+
185+
- ROS 2 nodes use standard DDS middleware
186+
- `zenoh-bridge-ros2dds` translates DDS ↔ Zenoh
187+
- ros-z communicates via Zenoh side of bridge
188+
189+
## Complete Example: Backend Selection
190+
191+
The `z_pubsub` example demonstrates backend selection for pub/sub communication. The example supports both `RmwZenoh` and `Ros2Dds` backends via command-line arguments:
192+
193+
```rust,ignore
194+
{{#include ../../../ros-z/examples/z_pubsub.rs}}
195+
```
196+
197+
Key features of this example:
198+
199+
- **Generic backend support**: Uses `KeyExprBackend` trait for compile-time backend selection
200+
- **CLI arguments**: Select backend with `--backend rmw-zenoh` or `--backend ros2-dds`
201+
- **Conditional compilation**: Ros2Dds backend only available with `--features ros2dds`
202+
- **Same high-level API**: Publisher and subscriber code identical except for backend type parameter
203+
204+
**Usage Examples:**
205+
206+
### Direct ros-z Communication (RmwZenoh backend - default)
207+
208+
```bash
209+
# Terminal 1: Run listener with default RmwZenoh backend
210+
cargo run --example z_pubsub -- --role listener
211+
212+
# Terminal 2: Run talker with default RmwZenoh backend
213+
cargo run --example z_pubsub -- --role talker
214+
```
215+
216+
### Interop with ROS 2 via zenoh-bridge-ros2dds (Ros2Dds backend)
217+
218+
1. **Terminal 1 - Start zenoh-bridge-ros2dds:**
219+
220+
```bash
221+
zenoh-bridge-ros2dds
222+
```
223+
224+
2. **Terminal 2 - Start ROS 2 DDS talker:**
225+
226+
```bash
227+
ros2 run demo_nodes_cpp talker
228+
```
229+
230+
3. **Terminal 3 - Run ros-z listener with Ros2Dds backend:**
231+
232+
```bash
233+
cargo run --example z_pubsub --features ros2dds -- --role listener --backend ros2-dds
234+
```
235+
236+
Or run ros-z talker and ROS 2 listener:
237+
238+
```bash
239+
# Terminal 2: Run ros-z talker with Ros2Dds backend
240+
cargo run --example z_pubsub --features ros2dds -- --role talker --backend ros2-dds --topic chatter
241+
242+
# Terminal 3: Run ROS 2 listener
243+
ros2 run demo_nodes_cpp listener
244+
```
245+
246+
## ros-z-console Backend Support
247+
248+
The `ros-z-console` monitoring tool supports backend selection via CLI:
249+
250+
```bash
251+
# Monitor rmw_zenoh-based systems (default)
252+
ros-z-console tcp/127.0.0.1:7447 0
253+
254+
# Monitor zenoh-bridge-ros2dds systems
255+
ros-z-console tcp/127.0.0.1:7447 0 --backend ros2dds
256+
257+
# Show help
258+
ros-z-console --help
259+
```
260+
261+
**Backend parameter:**
262+
263+
- `--backend rmw-zenoh` - Monitor rmw_zenoh systems (default)
264+
- `--backend ros2dds` - Monitor zenoh-bridge-ros2dds systems
265+
266+
The backend choice affects:
267+
268+
- Topic discovery key expressions
269+
- Rate measurement subscriptions
270+
- Multi-topic monitoring
271+
272+
```admonish tip
273+
Always match the backend in `ros-z-console` to your deployment architecture. Use `rmw-zenoh` for native Zenoh systems and `ros2dds` when monitoring systems bridged via `zenoh-bridge-ros2dds`.
274+
```
275+
276+
## Backend Comparison
277+
278+
### When to Use RmwZenoh Backend
279+
280+
**Use RmwZenoh when:**
281+
282+
- Building pure Zenoh-based ROS 2 systems
283+
- Using `rmw_zenoh_cpp` middleware
284+
- Requiring domain isolation
285+
- Deploying new systems with native Zenoh support
286+
- Maximizing Zenoh performance benefits
287+
288+
### When to Use Ros2Dds Backend
289+
290+
**Use Ros2Dds when:**
291+
292+
- Bridging existing DDS-based ROS 2 systems
293+
- Using `zenoh-bridge-ros2dds`
294+
- Integrating with legacy ROS 2 infrastructure
295+
- Gradual migration from DDS to Zenoh
296+
- Heterogeneous deployments (DDS + Zenoh)

ros-z-console/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description = "ROS 2 Graph Inspector & Dataflow Monitor TUI"
66
homepage = "https://github.com/ZettaScaleLabs/ros-z"
77

88
[dependencies]
9-
ros-z = { path = "../ros-z" }
9+
ros-z = { path = "../ros-z", features = ["ros2dds"] }
1010
clap = { workspace = true }
1111
crossterm = "0.29"
1212
parking_lot = { workspace = true }

ros-z-console/src/app/mod.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use parking_lot::Mutex;
2121
use ratatui::widgets::ScrollbarState;
2222
use rusqlite::Connection;
2323

24-
use crate::core::engine::CoreEngine;
24+
use crate::core::engine::{Backend, CoreEngine};
2525

2626
pub use state::*;
2727

@@ -33,6 +33,7 @@ pub struct App {
3333
pub connection_status: ConnectionStatus,
3434
pub config: Config,
3535
pub db_conn: Arc<Mutex<Connection>>,
36+
pub backend: Backend,
3637

3738
// Panel state
3839
pub current_panel: Panel,
@@ -151,13 +152,14 @@ impl App {
151152
session: core.session.clone(),
152153
connection_status: ConnectionStatus::Connected,
153154
config: config.clone(),
155+
db_conn,
156+
backend: core.backend,
154157
current_panel: Panel::Topics,
155158
selected_index: 0,
156159
quit: false,
157160
focus_pane: FocusPane::List,
158161
detail_state: DetailState::default(),
159162
live_metrics: Arc::new(Mutex::new(LiveMetrics::default())),
160-
db_conn,
161163
cached_topics: Vec::new(),
162164
cached_nodes: Vec::new(),
163165
cached_services: Vec::new(),
@@ -197,6 +199,21 @@ impl App {
197199
Config::default()
198200
}
199201

202+
/// Generate key expression for a topic based on the selected backend
203+
fn topic_key_expr(&self, topic: &str) -> String {
204+
let topic_name = topic.trim_start_matches('/');
205+
206+
match self.backend {
207+
Backend::RmwZenoh => {
208+
// rmw_zenoh format: <domain_id>/<topic>/**
209+
format!("{}/{}/**", self.core.domain_id, topic_name)
210+
}
211+
Backend::Ros2Dds => {
212+
// ros2dds format: <topic>/** (no domain prefix)
213+
format!("{}/**", topic_name)
214+
}
215+
}
216+
}
200217

201218
pub fn update_graph_cache(&mut self) {
202219
let graph = self.core.graph.lock();
@@ -445,8 +462,7 @@ impl App {
445462
topic: &str,
446463
duration_secs: u64,
447464
) -> Result<f64, Box<dyn std::error::Error + Send + Sync>> {
448-
let topic_name = topic.trim_start_matches('/');
449-
let key_expr = format!("{}/{}/**", self.core.domain_id, topic_name);
465+
let key_expr = self.topic_key_expr(topic);
450466

451467
let counter = Arc::new(Mutex::new(0usize));
452468
let counter_clone = counter.clone();
@@ -527,8 +543,7 @@ impl App {
527543

528544
// Create subscribers for each topic
529545
for topic in self.measuring_topics.clone() {
530-
let topic_name = topic.trim_start_matches('/');
531-
let key_expr = format!("{}/{}/**", self.core.domain_id, topic_name);
546+
let key_expr = self.topic_key_expr(&topic);
532547

533548
let metrics = self.topic_metrics.clone();
534549
let topic_clone = topic.clone();

0 commit comments

Comments
 (0)