ROS 2 Interoperability¶
hiroz nodes — whether written in Rust, Python, or Go — speak the same Eclipse Zenoh wire protocol as rmw_zenoh_cpp, the official ROS 2 middleware plugin for Zenoh. This means they interoperate transparently: a Go subscriber can receive messages from a ROS 2 C++ talker, a Python publisher can send to a Rust listener, and so on.
Mixing Humble with Jazzy, Kilted, or Lyrical?
If you need to bridge a Humble (legacy) network and a Jazzy / Kilted / Lyrical (modern) network, use hiroz-bridge from hiroz-toolkit. See the Cross-Distro Bridge chapter for details.
Prerequisites¶
- A ROS 2 installation with
rmw_zenoh_cppanddemo_nodes_cpp - A Zenoh router running on
localhost:7447(see Networking)
How It Works¶
All participants connect to the same Zenoh router on the local machine.
graph LR
accTitle: ROS 2 interop via shared Zenoh router with Rust, Python, and Go
accDescr: A ROS 2 C++ talker connects through rmw_zenoh_cpp to a local Zenoh router, which also serves hiroz nodes written in Rust, Python, and Go.
talker["demo_nodes_cpp<br>talker"] --> rmw["rmw_zenoh_cpp"] --> router(["Zenoh router<br>localhost:7447"])
router <-->|Zenoh| rust["Rust<br>(hiroz)"]
router <-->|Zenoh| python["Python<br>(hiroz-py)"]
router <-->|Zenoh| go["Go<br>(hiroz-go)"]
Note
The Zenoh router can be rmw_zenohd, zenohd, hu router, or Docker.
See Networking for all options.
Requirements for successful message exchange:
- Both sides must use the same message type with matching RIHS01 type hashes
- ROS 2 nodes must use
rmw_zenoh_cpp(export RMW_IMPLEMENTATION=rmw_zenoh_cpp)
Warning
If type hashes differ (e.g. mismatched message definitions), nodes silently drop messages.
Enable RUST_LOG=hiroz=debug to inspect the hash in the key expression and compare with the ROS 2 side.
See Troubleshooting for diagnosis steps.
Setup¶
# Terminal 1 — router
ros2 run rmw_zenoh_cpp rmw_zenohd
# Terminal 2 — ROS 2 talker
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 run demo_nodes_cpp talker
# Terminal 3 — hiroz listener (see Rust / Python / Go below)
Rust¶
For more detail on publisher/subscriber patterns see Pub/Sub.
Python¶
If you installed via pip (pre-built wheel), write your own listener.py using the code in Python Quick Start and run it with python listener.py.
If you cloned the repository (build from source), use the bundled example:
Or go the other way — Python publishes, ROS 2 listens:
python examples/topic_demo.py -r talker
# On ROS 2 side:
ros2 topic echo /chatter std_msgs/msg/String
For more detail see Python Bindings.
Go¶
For more detail see Go Quick Start.
Buffer messages from Lyrical publishers¶
A buffer-aware ROS 2 Lyrical publisher treats an unbounded uint8[] or char[] field as a buffer, directly or inside a nested message. An example is sensor_msgs/PointCloud2. For a matched subscriber that announces buffer support, the publisher writes the message on <topic key>/_buf_cpu and never on <topic key>. The humble, jazzy and kilted branches of rmw_zenoh have no buffer support: their liveliness key-expression code has no backends field.
| field | buffer |
|---|---|
unbounded uint8[], unbounded char[] |
yes |
unbounded byte[], unbounded int8[], bounded uint8[<=N], fixed uint8[N] |
no |
The released rmw_zenoh_cpp 0.10.6 package gave this table. Each field ran alone and nested, with n=3 runs.
A hiroz subscriber joins this group when its liveliness token ends in backends:cpu:. A joined subscriber declares two exact subscriptions, on <topic key> and on <topic key>/_buf_cpu, both before the token. The base key stays subscribed because a publisher that is not buffer-aware writes only that key. The publisher also writes keys such as <topic key>/_buf/<gid> for subscribers outside the group. A joined subscriber has no subscription that matches them. This is read from the source and is not measured.
Set the choice with with_buffer_group on a subscriber builder or a cache builder:
| setting | the subscriber joins when |
|---|---|
BufferGroup::Auto (default) |
the message has an unbounded uint8[] or char[], directly or nested, or hiroz has no message schema |
BufferGroup::Cpu |
always, except in the cases below |
BufferGroup::Off |
never |
- The typed code generator maps ROS
byte,uint8andcharto one type. A typed subscriber therefore also joins for an unboundedbyte[]. This costs one extra join and loses no data. - A subscriber with no message schema joins on every topic, not only on buffer topics. The feedback subscriber of an action client and the Python bindings have no message schema.
- The Python
create_subscriber(..., buffer_group=...)takes'auto','cpu'or'off'.'auto'and'cpu'do the same in Python. Any other string raisesValueError. - The status subscriber of an action client always stays out, because its message has no buffer. The feedback subscriber has no
Offswitch yet. - A raw subscriber has no message schema, so
Autojoins it.create_raw_subscriberandcreate_raw_subscriber_with_qosjoin, and so do the C and Go bindings that call them.create_raw_subscriber_with_grouptakesAuto,CpuorOff. It exists only with theffifeature, and the Go bindings have no entry point for it yet. The feedback subscriber of a raw action client reads nothing and stays out. - A subscriber that stays out subscribes to the base key only. A typed or Python subscriber that stays out keeps its liveliness token, without the
backends:cpu:part. A raw subscriber that stays out declares no token. RawSubscriberhas no public field. In release 0.2.0 theinnerfield is public, with theffifeature. A caller ofraw.inner.undeclare().wait()must callraw.undeclare()now. That call stops the token and both subscriptions. Dropping the wrapper stops the same handles.- A transient-local subscriber never joins, and
Cpudoes not change that, because it reads its cached history from the base key.rmw_zenoh_cppdoes the same: 0.10.6 and rolling at 678931f disable buffer endpoints for transient-local subscribers, and rolling does so for publishers. This limit concerns buffer transport and does not disable transient-local QoS. A subscriber on a key format other thanrmw_zenoh's never joins, under every setting. rmw-zenoh-rssubscriptions useOff.
A transient-local subscriber reads its history from <topic key>. The released 0.10.6 package gave this result in 75 cells with Image topics: five kinds of late joiner, three publish-time situations, n=5 per cell. The history arrived on <topic key> in every cell and never on <topic key>/_buf_cpu. No test covers history after ros2/rmw_zenoh#1078.
If you use router access control, allow <topic key> and <topic key>/_buf_cpu. A joined subscriber declares only these two keys, so a rule on <topic key>/** is not needed. A rule that allows <topic key> and not <topic key>/_buf_cpu stops a joined subscriber from receiving buffer messages. build returns Ok and no error appears. The base key still delivers. A subscriber receives a message that carries no buffer through the base key, so a rule on the group key does not affect that message. This holds for a subscriber with no message schema and for a subscriber set to Cpu on a topic that carries no buffer. Off stops a subscriber from joining, so Off needs no rule for the group key. A subscriber with no backends: field receives no buffer message from a buffer-aware publisher until a release carries ros2/rmw_zenoh#1078. Off therefore does not bring those messages back. hiroz logs each join at debug level and names both keys.
A subscriber with no backends: field still receives topics that carry no buffer through the base key. A buffer-aware publisher writes a message that carries a buffer on the group key only, so this subscriber receives nothing for it. The fix in ros2/rmw_zenoh#1078 is not released. This limit covers Off subscribers, transient-local subscribers, subscribers on a key format other than rmw_zenoh's and rmw-zenoh-rs subscriptions. Measurements cover an Off subscriber only. No test runs the other three.
| Limit | Cause | Measured | Fix |
|---|---|---|---|
| A joined subscriber reports lost-message events for messages that arrived | The publisher advances one sequence number for each endpoint copy | The lost count was K times the delivered messages for K other buffer-aware subscribers. K was 1 and 4, n=5 per K, one machine, 100 messages per second for 20 s, released 0.10.6 package. An unreleased revision gave the same. | The open fix ros2/rmw_zenoh#1079 gave a count of 0 (n=5 per K) |
No hiroz test covers this limit, and hiroz code does not change for it.