Using RMW_ZENOH with free_fleet (#697)

Posted by @Oussama-Dallali99:

Hey all,

I’m testing a setup where I replace zenoh_bridge_ros2dds with rmw_zenoh in the communication between the robot itself and its adapter

Plan:

  • Use rmw_zenoh on one robot.
  • Test critical topics (/tf, /battery_state, etc.).
  • See if actions and services still work fine cross-RMW.

Any tips, caveats, or things to look out for?

thanks !

Posted by @aaronchongth:

Hey @Oussama-Dallali99! Since you’re mentioning the zenoh bridge, I’m going to assume this is about free_fleet. Otherwise, let me know if you’re considering the general RMF fleet adapter cases.

The main potential blocker that comes to mind would be the difference in how the zenoh key expressions are handled between zenoh_brdige_ros2dds and rmw_zenoh. You can see take a look at Migration from `zenoh-plugin-ros2dds` to `rmw_zenoh` · Issue #522 · ros2/rmw_zenoh · GitHub for more information

But the gist is that at the moment, we are using the bridge to filter topics/services/actions, and add namespaces appropriately, so that the sources and targets of these topics/services/actions can be identified correctly. Bridge namespacing is quite straightforward, where the namespace is placed before the mangled zenoh key,

  • before namespace - /chatter
  • after namespace - /bridge_namespace/chatter
    This allows us to handle the keys as such, and rely on the bridge to work with the correct ROS_DOMAIN_ID on the robot via the bridge config.

However with rmw_zenoh, the key expressions contain a lot more information, including the ROS_DOMAIN_ID as well as other pieces of information that probably allows rmw_zenoh to function as an RMW implementation, and translating ROS 2 topic names → zenoh key expressions → ROS 2 topic names

  • before namespace via session config - <ROS_DOMAIN_ID>/chatter/...
  • after namespace via session config - my/namespace/<ROS_DOMAIN_ID>/chatter/...

Note that these are zenoh key expressions, and because of the namespace, rmw_zenoh will not parse the namespaced key expression back into the /chatter ROS 2 topic by default, and you will need the adapter to also be running a namespaced session (with the same namespace) in order to get receive the messages, which will not work when there are multiple robots with multiple namespaces.

There is however another way to go about it, but it could get tedious. Which is to use rmw_zenoh between the robots (each with their own namespace) and the adapter, while the adapter still uses raw zenoh key expressions to handle the information serialization, deserialization, sending and receiving, plus all the liveliness tokens, etc.

Posted by @Oussama-Dallali99:

There is however another way to go about it, but it could get tedious. Which is to use rmw_zenoh between the robots (each with their own namespace) and the adapter, while the adapter still uses raw zenoh key expressions to handle the information serialization, deserialization, sending and receiving, plus all the liveliness tokens, etc.

Also, regarding your second suggestion:

Which is to use rmw_zenoh between the robots (each with their own namespace) and the adapter, while the adapter still uses raw zenoh key expressions to handle the information serialization, deserialization, sending and receiving, plus all the liveliness tokens, etc.

Does This aligns well with how the nav1_robot_adapter.py and nav2_robot_adapter.py are implemented ? — the adapter uses raw Zenoh (not rclpy), and handles all pub/sub with zenoh_session.declare_publisher / declare_subscriber.

So just confirming: in this setup, I should continue to follow the same pattern from the nav1/nav2 adapters for interacting with Zenoh — and ensure that my robots using rmw_zenoh are configured to publish within properly namespaced sessions, correct?

Appreciate any thoughts or config tips!.

Thanks again,


Edited by @Oussama-Dallali99 at 2025-06-11T19:29:04Z

Posted by @Oussama-Dallali99:

Thanks for the detailed response — very helpful!
So based on your explanation, I’ll go ahead and namespace my robot from the ROS2 launch file to keep things organized on the robot side.
As for Zenoh:
To clarify, when using rmw_zenoh on the robot, how should I configure the Zenoh router and client sessions to handle these namespaced key expressions properly? Should I namespace them explicitly in the session config, or is there a preferred way to ensure correct key visibility across robots?
Additional context: I already made my own fleet adapter and it’s tested with the ros2dds_bridge, but we don’t want any DDS anymore and are moving to a fully Zenoh-based architecture.

Posted by @aaronchongth:

Does This aligns well with how the nav1_robot_adapter.py and nav2_robot_adapter.py are implemented ? — the adapter uses raw Zenoh (not rclpy), and handles all pub/sub with zenoh_session.declare_publisher / declare_subscriber.

Yes, that is how it is implemented. But by virtue of using zenoh bridges, the key expressions wrangling become simpler, also without the liveliness tokens, etc.

So just confirming: in this setup, I should continue to follow the same pattern from the nav1/nav2 adapters for interacting with Zenoh — and ensure that my robots using rmw_zenoh are configured to publish within properly namespaced sessions, correct?

To clarify, when using rmw_zenoh on the robot, how should I configure the Zenoh router and client sessions to handle these namespaced key expressions properly? Should I namespace them explicitly in the session config, or is there a preferred way to ensure correct key visibility across robots?

I’d say this is up to you. Since Zenoh is highly configurable, it will be up to you to design where namespacing happens. I’m going to assume that every robot will require a local zenoh router, so namespaces can be configured on your ROS 2 launches, or robot zenoh session config, or robot zenoh router config. I’ve not looked into this enough to have a clear opinion unfortunately, you might have to look into the pros and cons of each method more.

Additional context: I already made my own fleet adapter and it’s tested with the ros2dds_bridge, but we don’t want any DDS anymore and are moving to a fully Zenoh-based architecture.

That’s awesome! Free fleet is designed to work with identical generically configured robots (per Nav2 or Nav1 tutorials, without any additional namespacing), to maximize the out-of-the-box experience. So if the robot is already working on its own, adding in the bridge + config will get it integrated automatically without changing the inner configuration of the robot. In your case, you might be considering other priorities, so I’d say the design decisions might be up to you now

Posted by @Oussama-Dallali99:

hey @aaronchongth !Thanks again for the insights! I’d like to validate whether my current approach to reading raw rmw_zenoh traffic is robust and aligned with how rmw_zenoh constructs keys and publishes CDR-encoded messages.


What I’m Doing

I’m writing a custom Zenoh subscriber (example coming soon) that listens for TF data directly from rmw_zenoh, without relying on ROS 2 or DDS. Here’s what I’m doing:


Zenoh Key Construction

I replicate the rmw_zenoh key pattern like this:

{domain_id}/{encoded_namespace}/{topic}/{8-char md5(message_type)}

For example, for the /tf topic (tf2_msgs/msg/TFMessage), I get:

0/tf/2ac9c305

I’m also supporting namespaced keys like:

0/foo%2Fbar/tf/2ac9c305

based on whether a namespace is provided.


Wildcard Subscriptions

I subscribe to the following patterns:

patterns = [
    "0/*/tf*/**",       # Catch tf / tf_static with optional namespaces
    "0/tf/**",          # Direct dynamic TF
    "0/tf_static/**"    # Direct static TF
]

The idea is to support robots in both root and nested namespaces.


Handling Incoming Data

Once data arrives, I:

  • Parse the key to infer domain, namespace, topic, and hash.
  • Inspect the payload, which is typically raw CDR.
  • I leave the CDR payload untouched for now (just show raw bytes), unless I know it’s JSON (e.g., for test stubs).

My Question

Given the above:

Is this key construction and subscription approach valid and future-proof to run the fleet adapter(/tf,/battery_state,naviagtion)
, when targeting robot that is using rmw_zenoh ?

Would appreciate any feedback on edge cases I may be missing or things that could break with future updates.

Thanks again!


Edited by @Oussama-Dallali99 at 2025-06-24T14:18:50Z

Posted by @aaronchongth:

Hey @Oussama-Dallali99! Glad to hear you got something working.

Is this key construction and subscription approach valid and future-proof to run the fleet adapter(/tf,/battery_state,naviagtion)
, when targeting robot that is using rmw_zenoh ?

Would appreciate any feedback on edge cases I may be missing or things that could break with future updates.

I can’t comment much on future development of rmw_zenoh. Since the implementation lives in the ROS 2 community, I’d imagine long term backward compatibility will be achieved with their semantic versioning scheme. However as you have surmised, there is no guarantee that this schema will maintain in the same form in the future, especially when a major version release comes by, which allows for breaking changes.

Technically, the schema of key expressions might be considered implementation details too, and may not be considered API, so there may also be a chance that it can change without a major release.

For more concrete information, you might need to engage the rmw_zenoh folks directly