HORUS/blog

Sep 5, 2026 · zenoh · dds · ros-2 · middleware-comparison

Is Zenoh a Real Alternative to DDS?

Zenoh is a real alternative to DDS for links that leave the robot, and ROS 2 supports it. On-robot timing is a separate problem no transport choice solves.

Yes, Zenoh is a real alternative to DDS for anything leaving the robot, though neither Zenoh, DDS nor HORUS fixes on-robot timing. Zenoh was built for links that lose packets and networks that span buildings, which is where DDS discovery gives up first, and ROS 2 offers a Zenoh option. The verdict flips when everything with a deadline lives on one computer, where the transport hardly matters. This post is for anyone who has read that ROS 2 can run on Zenoh and wants to know whether switching changes anything on their own robot.

The question usually arrives after a bad month. The robot works on the bench and misbehaves on the floor. The node list is complete on one run and missing half its entries on the next. Someone on the team has already tried a different DDS implementation, which changed the shape of the failure without removing it, and someone else has spent an afternoon reading about multicast settings on an access point nobody in the office controls. Then a release note goes around saying ROS 2 can run on something called Zenoh, and a forum post says it fixes discovery, and the team splits into the half that wants to try it this week and the half that thinks the last transport change cost two weeks and bought nothing. Nobody in the room can say what the new option actually changes, because everything written about it is either a specification or a benchmark, and neither answers the only question that matters: will the robot behave differently on Monday, and what will it cost to find out.

Should I switch my robot from DDS to Zenoh?

Switch if your traffic crosses a network you do not control, and stay put if it does not. That single test decides it for most teams. If your nodes all live on one computer, or on two computers sharing a switch in a lab, DDS is doing its job in the environment it was designed for, and a transport change buys you a new set of configuration files and a new set of unfamiliar error messages in exchange for nothing you can feel. If any part of your graph crosses Wi-Fi, crosses a building, sits behind a home router, or has to reach a machine in a data centre, the picture inverts. That is the exact situation where DDS discovery, which expects to shout on a local network and be heard, quietly stops working, and where the alternative was designed from the beginning to connect through a known address rather than by shouting. The decision is not about which is technically better. It is about whether your network matches the assumptions each one was built on, and a network you do not administer almost never matches the older set.

What does the transport underneath ROS 2 actually do?

The transport is the layer that finds the other side and moves bytes to it, and everything above it is unaware of how that happens. When you write a publisher, you name a topic and a message type. Something underneath has to work out which processes on which machines care about that topic, agree with each of them on what the message looks like, turn your object into bytes, get those bytes across whatever lies between the two machines, and decide what to do when some of them do not arrive. ROS 2 keeps that layer swappable on purpose, behind an interface, so the same node source can run over different implementations without edits. That is why this whole question exists as a question. Two robots running identical code can behave completely differently under stress because the layer nobody looks at made different choices about discovery, queueing and retries. If the general shape of that layer is still fuzzy, a plain-English tour of what middleware does in a robot is worth reading before you compare implementations of it.

What makes Zenoh behave differently from DDS?

The difference that matters in practice is how each one finds the other side. DDS discovery is decentralised and chatty by design: participants announce themselves to the local network, everyone hears everyone, and the graph assembles itself with no central component. That is elegant on a wired switch and it is the single biggest source of pain everywhere else, because the announcements travel as group-addressed traffic and most networks treat group-addressed traffic as optional. Zenoh takes the opposite stance. Peers can still find each other automatically on a friendly network, but the normal arrangement is that everything connects to a known router, and routers connect to each other, so a robot behind a home router can reach a machine in another city without anyone configuring a tunnel. The second difference is reach: the design assumes lossy, high-delay links exist and treats them as ordinary rather than exceptional. The third is that a router is now a thing you run, supervise and restart, which is a genuine operational cost that the decentralised approach did not have.

What are my actual options for moving messages around a robot?

There are seven realistic options and most robots use two of them at once. You can keep ROS 2 on its default DDS, which is right whenever your machines share a switch. You can keep DDS but make discovery explicit with a discovery server or a static peer list, which fixes a surprising share of Wi-Fi problems without changing anything else. You can move ROS 2 onto Zenoh, keeping every node, topic and message type and changing only the layer underneath. You can use Zenoh on its own, without ROS 2, when the thing you are connecting is not a robot node at all. You can leave a DDS graph on the robot and put a bridge at the edge, so only one connection crosses the difficult link. You can put the timing-critical part of the graph on the robot's own computer, which is what HORUS is for, since Rust, Python and C++ processes there share shared-memory ring buffers instead of serialising to each other, and what ROS 2 approximates with composed nodes. Or you can send telemetry over an ordinary web transport and stop pretending the dashboard is part of the robot.

Which option fits the robot I am building?

The option that fits depends on where your messages actually have to travel, not on which layer has the most interesting design. Before reading a single row, draw your robot as boxes and lines and mark every line that leaves the machine it started on. Most people discover fewer such lines than they expected, and one of them turns out to be carrying something nobody meant to send across a radio. That drawing decides more of the table than any comparison of features does, because each row below is really a statement about one kind of line.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
ROS 2 on its default DDSLab robots and wired setupsOrdinary ROS 2 usageEvery machine shares a switchTraffic crosses a network you do not run
ROS 2 on DDS with explicit discoveryTeams staying on DDS but on Wi-FiPeer lists and clean restartsThe node list is known in advanceNodes and robots come and go freely
ROS 2 on ZenohTeams whose graph spans networksRunning and supervising a routerThe link is lossy, remote or firewalledThe whole graph fits on one switch
Zenoh without ROS 2Fleet backends and web servicesIts own tooling, and no ROS packagesThe endpoints are servers, not nodesYou want ROS drivers and visualisers
A bridge at the edge of the robotAnyone streaming to an operatorWhich topics a person truly needsThe robot is autonomous, the link is for watchingAn operator closes the loop by hand
HORUS on the robot's own computerBuilders mixing Rust, Python and C++Those languages, and life outside the ROS package setSensing and control share one machineYou need ROS drivers, or several machines
A web transport for telemetryDashboards and remote monitoringBasic web plumbingHumans read the data, robots do notSomething on the robot waits for it

Two rows are usually the honest answer: one for what happens on the robot, one for what leaves it.

What should I do if I am one person with one robot?

Do not switch transports until you have proved the transport is the problem. One person cannot afford a migration that turns out to be unrelated, and the test that separates the cases takes minutes. Run the whole system over a cable. If the graph is complete and steady, discovery over your radio is the culprit, and a transport that connects to a known address instead of shouting on the network genuinely helps. If the trouble persists over a cable, the transport is innocent and you have an ordinary bug wearing a costume. Assuming the cable test points at the network, try explicit discovery on what you already run before you migrate: a static peer list or a discovery server is an afternoon of work, is reversible, and resolves a good share of these cases. Migrate when that fails, or when your robot has to reach something outside the building, which is the case where the older design has no good answer and no amount of configuration produces one. Solo builders should spend their migrations carefully.

What if my robot is a small board talking to a laptop?

That arrangement is where a transport change pays off most, and where an architecture change pays off more. A small board and a laptop on the same Wi-Fi network is the classic case: the board has one radio competing for airtime, the laptop has power management on its own radio, and the announcements that let them find each other are the first traffic the access point throws away. Explicit connections fix the finding-each-other half, and that alone converts an intermittent nightmare into a system that starts up reliably. What no transport fixes is the second half, which is that a radio between two halves of a control loop will drop the largest messages first and stall at the worst moment. So use the better transport for the link, and simultaneously move anything with a deadline onto the board itself. The reason those two things get confused is that they solve adjacent problems: one makes the link connect, the other makes the robot not care whether the link connected. The second is what you actually want.

What if I have to demo this in two weeks?

Do not change the transport before a demo. A transport swap is a change to the layer that everything else depends on, and its failures show up in the least convenient places: a service call that hangs only under load, a node that connects on the bench and not in the venue, a tool that used to work and now reports nothing. Two weeks is enough time to make that change and not enough time to discover what it broke. Do the reversible things instead. Give discovery an explicit list of addresses so start-up stops depending on the venue's network. Move anything a person is not looking at onto the robot, so the wireless link carries only a video feed and a few commands. Make the robot behave sanely when messages stop, which for a demo means slowing and stopping rather than continuing on the last thing it heard. Then plan the migration for the week after the demo, when a surprise costs you a day rather than the room. Demo halls are the worst radio environments most robots ever see.

What if I have never configured a middleware layer before?

Then start with the observation that most of the settings do not matter and two of them matter enormously. The two are how nodes find each other, and what happens to a message when a reader is not keeping up. Everything else is detail you can leave alone for a long time. Discovery is the one you are probably fighting: the default behaviour is to announce on the local network and hope, and the fix on an unfriendly network is to tell each process exactly where to connect. That is a list of addresses, not a discipline. Delivery is the one that bites next: a reliable setting means a slow reader slows down the writer, a best-effort setting means old data is discarded so new data can pass, and picking wrong is behind a large share of the strange behaviour people blame on hardware. If you want the longer version of that second point, the way robots quietly lose sensor data is the same mechanism seen from the other end. Learn those two and skip the rest until something forces you back.

What do I give up by moving to Zenoh?

You give up maturity in the surrounding ecosystem and you take on a process to run, and both are real. The DDS implementations under ROS 2 have been in industrial service for years, which means their strange behaviour is documented, their configuration files have been argued about in public, and a person you hire has probably met them. The newer option has fewer such stories, so when something behaves oddly you may be the first person to write it down, and that costs time you did not budget. You also acquire a router: a process that must start before your nodes, stay alive, be restarted after a crash, and be supervised on a robot that boots without a keyboard attached. That is a small ongoing cost rather than a large one, but it is a cost the decentralised design does not have. Finally, you give up a little certainty about tooling. Most things work, some things assume the older layer underneath, and finding out which is which is your afternoon rather than someone else's.

When is ROS 2 the better choice?

ROS 2 is the better choice for nearly everyone reading this, on either transport, and the question of which layer sits underneath does not change that. The reason is that the value is above the transport, not in it: drivers for the lidar and depth camera you bought, a navigation stack that works, an arm planner, a simulator that already speaks the same message types, a visualiser that shows you what the robot thinks, and a hiring pool of people who have seen all of it. No transport choice adds or removes any of that. ROS 2 is also the right answer whenever your graph must span machines, because splitting work across computers is a first-class part of the design. HORUS is not the answer there and does not try to be: shared memory is a single-machine idea, and middleware that stops messages being serialised between processes on one computer has nothing to offer a link between two of them. Pick the transport that matches your network, and stop treating the choice as a referendum on the framework.

Will Zenoh make my robot respond faster?

No, and here is why: the transport is rarely the reason a robot responds slowly. The usual reasons are a callback doing heavy work while other callbacks wait behind it, a message being copied and serialised several times on its way across one machine, a sensor driver that was written for convenience rather than for a loop, and a control loop competing with a logger for the same processor. A different transport changes none of those. What a transport change genuinely fixes is a system that does not connect, connects unpredictably, or falls apart when the link is poor, and those are worth fixing on their own terms. The migration that disappoints people is the one undertaken as a speed fix, because the team arrives at the far side with the same delays, a new set of configuration files and two weeks gone. Find out where your time is actually going before you move layers. If the answer is anywhere inside one machine, the network was never involved.

Is DDS the reason my robot's timing is unpredictable?

Partly, but not the way you think. DDS does contribute, and the contribution is specific: discovery traffic arrives in bursts and competes with your data, retransmissions on a poor link crowd out new messages, and reliable delivery to a slow reader slows the writer down, which is felt in the sensor driver rather than in the code that caused it. Those are real and a different transport handles some of them better. The trap is treating that as the whole story. If your timing wobbles while everything runs on one machine and nothing crosses a network, the layer you are blaming is barely involved, and the causes are in scheduling, in copies, and in what your operating system is doing when the robot gets busy. It is worth reading about why an ordinary wireless network defeats these assumptions before deciding which half you have. Run the loop with the network disconnected. If it still wobbles, the transport is not on trial.

How do I decide whether to switch?

Answer three questions in order and the choice makes itself. First, does anything with a deadline cross a network? If not, stop here and keep what you have, because you would be paying a migration cost for a property you do not use. Second, if something does cross a network, do you control that network? If it is a switch in your lab, make discovery explicit and stay put. If it is a venue's Wi-Fi, a customer's building, or the open internet, the newer design was built for exactly that and the older one was not, and no amount of tuning closes that gap. Third, can you carry an extra process on the robot? A router that must start, stay up and be restarted is trivial on a machine with an init system and irritating on a board someone reflashes weekly. Those three answers land almost everyone in one of two places: keep DDS and make discovery explicit, or move the transport and keep the graph otherwise untouched. Both are ordinary engineering decisions rather than allegiances.

A short version, by situation:

When you are weighing whole stacks rather than one layer, the HORUS Fit Framework compares them on five things that are not numbers: ecosystem size, setup effort, team size fit, deployment target, and licence. Those five settle far more arguments than a measurement does, because the thing that sinks a robot project is rarely the layer that got benchmarked.

HORUS is open source under Apache-2.0 and the repository is linked below. Star it so it is in your list when you start building.

Found this useful? Share it:Discuss on HNShare on X