Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Key Expression Formats

ros-z uses key expression formats to map ROS 2 entities (topics, services, actions) to Zenoh key expressions. The format is provided by the independent ros-z-protocol crate and determines how ROS 2 names are translated for Zenoh routing and discovery.

Note

Key expression format is a runtime choice that affects how ros-z maps ROS 2 entities to Zenoh key expressions. Choose the format that matches your infrastructure for proper message routing.

Available Formats

ros-z supports multiple key expression formats for interoperability with different Zenoh-ROS bridges:

FormatCompatibilityUse Case
RmwZenoh (default)rmw_zenoh_cppStandard ROS 2 Zenoh middleware
Ros2Ddszenoh-bridge-ros2ddsDDS bridge compatibility

RmwZenoh Format (Default)

The RmwZenoh format is designed for compatibility with ROS 2's official Zenoh middleware implementation (rmw_zenoh_cpp).

Key Expression Patterns:

Topic keys:      <domain_id>/<topic>/<type>/<hash>
Liveliness:      @ros2_lv/<domain_id>/<entity_kind>/<namespace>/<name>/...

Example Topic Keys:

0/chatter/std_msgs::msg::dds_::String_/RIHS01_...
5/robot/sensors/camera/sensor_msgs::msg::dds_::Image_/RIHS01_...

Use this format when:

  • Using rmw_zenoh_cpp as your ROS 2 middleware
  • Running pure ros-z deployments
  • Requiring domain isolation via Zenoh

Ros2Dds Format

The Ros2Dds format is designed for compatibility with zenoh-bridge-ros2dds, which bridges standard DDS-based ROS 2 nodes to Zenoh.

Key Expression Format:

<topic>/**

Example:

chatter/**                    # Topic /chatter (no domain prefix)
robot/sensors/camera/**       # Topic /robot/sensors/camera

Use this format when:

  • Bridging existing DDS-based ROS 2 systems to Zenoh
  • Using zenoh-bridge-ros2dds
  • Integrating with CycloneDDS or FastDDS nodes via Zenoh

Key Expression Behavior (IMPORTANT)

Understanding how topic names are converted to key expressions is critical for debugging:

Topic Key Expressions (For Data Routing)

ALL entity types (publishers, subscriptions, services, clients, actions) use strip_slashes() behavior:

  • Removes leading and trailing slashes only
  • Preserves internal slashes for hierarchical routing
  • Enables multi-segment topic names

Examples:

ROS 2 Topic NameTopic Key Expression✓/✗
/chatter0/chatter/...✅ Correct
/robot/sensors0/robot/sensors/...✅ Correct
/a/b/c0/a/b/c/...✅ Correct
/talker/service0/talker/service/...✅ Correct

Why preserve slashes?

  • Zenoh uses / for hierarchical routing
  • Enables wildcard subscriptions: 0/robot/**
  • Human-readable key expressions

Liveliness Tokens (For Discovery)

ALL fields in liveliness tokens use mangle_name() behavior:

  • Replaces all / with %
  • Ensures unambiguous parsing of entity metadata
  • Machine-parsable format for discovery protocol

Examples:

ROS 2 NameLiveliness Field✓/✗
/chatter%chatter✅ Correct
/robot/sensors%robot%sensors✅ Correct
/my_node%my_node✅ Correct

Why mangle slashes?

  • Liveliness tokens have fixed structure: @ros2_lv/<domain>/<kind>/<ns>/<name>/...
  • Prevents ambiguity when parsing fields
  • Ensures reliable entity discovery

Why Two Different Behaviors?

This is intentional design in rmw_zenoh_cpp, not an inconsistency:

  • Topic keys: Human-readable, hierarchical (optimized for Zenoh routing)
  • Liveliness: Machine-parsable, unambiguous (optimized for discovery protocol)

Tip

If multi-segment topics like /robot/sensors/camera don't receive messages, check your ros-z version. Versions before 0.1.0 had a bug where publishers incorrectly mangled topic key expressions.

API Usage

Specifying Format at Context Creation

use ros_z::context::ZContextBuilder;
use ros_z_protocol::KeyExprFormat;

// Default (RmwZenoh)
let ctx = ZContextBuilder::default().build()?;

// Explicit format selection
let ctx = ZContextBuilder::default()
    .keyexpr_format(KeyExprFormat::RmwZenoh)
    .build()?;

// Ros2Dds format for DDS bridge compatibility
let ctx = ZContextBuilder::default()
    .keyexpr_format(KeyExprFormat::Ros2Dds)
    .build()?;

Key points:

  • Format is set at context creation time
  • All nodes and entities created from the context use the same format
  • Default format is KeyExprFormat::RmwZenoh
  • Format choice is type-safe and explicit

Creating Entities

Once the context is created with a format, all entities inherit it:

use ros_z_msgs::std_msgs::String as RosString;
use ros_z::Builder;

// Create context with RmwZenoh format (default)
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("my_node").build()?;

// Publisher uses context's format
let pub_rmw = node
    .create_pub::<RosString>("chatter")
    .build()?;

// Subscriber uses same format
let sub_rmw = node
    .create_sub::<RosString>("chatter")
    .build()?;

Mixing Formats (Advanced)

To communicate with both RmwZenoh and Ros2Dds systems, create separate contexts:

// Context for rmw_zenoh_cpp nodes
let ctx_rmw = ZContextBuilder::default()
    .keyexpr_format(KeyExprFormat::RmwZenoh)
    .build()?;

// Context for zenoh-bridge-ros2dds nodes
let ctx_dds = ZContextBuilder::default()
    .keyexpr_format(KeyExprFormat::Ros2Dds)
    .build()?;

// Create nodes from each context
let node_rmw = ctx_rmw.create_node("rmw_node").build()?;
let node_dds = ctx_dds.create_node("dds_node").build()?;

Architecture Diagrams

RmwZenoh Format Architecture

graph LR
    A[ros-z Node<br/>RmwZenoh Format] -->|"0/chatter/**"| B[Zenoh Router<br/>rmw_zenoh]
    B -->|"0/chatter/**"| C[ROS 2 Node<br/>rmw_zenoh_cpp]

Use case: Native Zenoh-based ROS 2 deployment

  • All nodes use rmw_zenoh or ros-z
  • Direct Zenoh communication
  • Domain isolation via key expression prefix

Ros2Dds Format Architecture

graph LR
    A[ros-z Node<br/>Ros2Dds Format] -->|"chatter/**"| B[zenoh-bridge-ros2dds<br/>Router + Bridge]
    B -->|DDS| C[ROS 2 Node<br/>CycloneDDS/FastDDS]

Use case: Bridge existing DDS systems to Zenoh

  • ROS 2 nodes use standard DDS middleware
  • zenoh-bridge-ros2dds translates DDS ↔ Zenoh
  • ros-z communicates via Zenoh side of bridge

Key Expression Generation Details

Understanding how ros-z-protocol generates key expressions helps with debugging and monitoring.

Topic Key Expression Structure

<domain_id>/<topic_stripped>/<type>/<hash>

Components:

  1. Domain ID: ROS 2 domain (e.g., 0, 5)
  2. Topic (stripped): Topic name with leading/trailing slashes removed, internal slashes preserved
  3. Type: Mangled message type (e.g., std_msgs::msg::dds_::String_)
  4. Hash: Type hash for compatibility (e.g., RIHS01_...)

Example:

Topic: /robot/sensors/camera
Type:  sensor_msgs/msg/Image
Hash:  RIHS01_abc123...

Key Expression:
0/robot/sensors/camera/sensor_msgs::msg::dds_::Image_/RIHS01_abc123...

Liveliness Token Structure

@ros2_lv/<domain>/<entity_kind>/<zid>/<id>/<namespace>/<name>/<type>/<hash>/<qos>

All name fields are mangled (/ → %):

Namespace: /robot/arm  →  %robot%arm
Name:      /gripper    →  %gripper

Example:

@ros2_lv/0/MP/01234567890abcdef/1/%robot%arm/%gripper/std_msgs::msg::String_/RIHS01_.../qos_string

ros-z-protocol Crate

The key expression logic is provided by the independent ros-z-protocol crate:

Features:

  • no_std compatible (with alloc)
  • Language-agnostic protocol layer (FFI-ready)
  • Feature-gated format implementations
  • Comprehensive unit tests
  • Type-safe API

Cargo features:

[dependencies]
ros-z-protocol = { version = "0.1", features = ["rmw-zenoh", "ros2dds"] }

Using ros-z-protocol directly:

use ros_z_protocol::{KeyExprFormat, entity::*};

let format = KeyExprFormat::default(); // RmwZenoh

// Generate topic key expression
let topic_ke = format.topic_key_expr(&entity)?;

// Generate liveliness token
let lv_ke = format.liveliness_key_expr(&entity, &zid)?;

// Parse liveliness token back to entity
let parsed_entity = format.parse_liveliness(&lv_ke)?;

See the ros-z-protocol documentation for details.

Troubleshooting

Multi-Segment Topics Not Working?

Symptom: Publisher publishes to /robot/sensors/camera but subscriber never receives messages.

Cause: Old versions of ros-z (before 0.1.0) incorrectly mangled slashes in topic key expressions.

Fix: Update to ros-z 0.1.0+ which correctly uses strip_slashes() for all topic key expressions.

Verify: Enable debug logging to check key expressions:

RUST_LOG=ros_z=debug cargo run --example z_pubsub

Look for key expressions like:

✅ Correct:  0/robot/sensors/camera/sensor_msgs::msg::Image_/...
❌ Wrong:    0/robot%sensors%camera/sensor_msgs::msg::Image_/...

No Messages Between ros-z and rmw_zenoh_cpp?

Check format: Ensure ros-z uses KeyExprFormat::RmwZenoh (the default).

Check type hash: Enable debug logging and compare type hashes:

RUST_LOG=ros_z=debug cargo run

Type hashes must match between ros-z and rmw_zenoh_cpp. If they don't, you may have:

  • Different message definitions
  • Different ROS 2 distros
  • Outdated generated messages

No Messages Through zenoh-bridge-ros2dds?

Check format: Ensure ros-z uses KeyExprFormat::Ros2Dds:

let ctx = ZContextBuilder::default()
    .keyexpr_format(KeyExprFormat::Ros2Dds)
    .build()?;

Check bridge configuration: Verify zenoh-bridge-ros2dds is running and connected to the same Zenoh router as ros-z.

Format Comparison

When to Use RmwZenoh Format

Use RmwZenoh when:

  • Building pure Zenoh-based ROS 2 systems
  • Using rmw_zenoh_cpp middleware
  • Requiring domain isolation
  • Deploying new systems with native Zenoh support
  • Maximizing Zenoh performance benefits

When to Use Ros2Dds Format

Use Ros2Dds when:

  • Bridging existing DDS-based ROS 2 systems
  • Using zenoh-bridge-ros2dds
  • Integrating with legacy ROS 2 infrastructure
  • Gradual migration from DDS to Zenoh
  • Heterogeneous deployments (DDS + Zenoh)