HORUS/blog

Sep 5, 2026 · ros2 · middleware · robot-software · comparison

HORUS vs ROS 2: Which Should You Build On?

Build on ROS 2 for its packages and tooling; build on HORUS when late messages are what your robot's motion keeps showing. Here is how to tell which you are.

Build on ROS 2 if you need its ecosystem; build on HORUS if the control loop is the part that keeps failing. ROS 2 gives you navigation, mapping, drivers and tooling that already work, which usually matters more than message timing. The verdict flips when late messages start showing up as visible motion: a stutter, an overshoot, an arm that stops late. The rest of this post is for engineers who already have a ROS 2 robot working and are deciding whether the next one should be built differently.

You know the shape of the complaint before you can name it. The robot works on the bench and then does something slightly different every third run. A colleague asks why the arm stopped a centimetre short and you do not have an answer, only a theory. You have started reading about quality-of-service settings you never wanted to learn, and there is a note somewhere listing which nodes you merged into one process and why. Somebody suggested turning the logging down and it helped, which is irritating, because logging is how you find things. The build takes long enough that you have quietly stopped trying small experiments. You have three browser tabs open comparing transport layers and none of them are about your robot.

Meanwhile the parts of the project you are proud of, the estimator and the gait and the grasp, are fine, and have been fine for months. The uncomfortable question is whether you have a robot problem or a plumbing problem, and whether the honest answer means starting over.

Which one should you actually build your robot on?

Build on ROS 2 unless timing is the thing breaking your robot. Most robots fail for reasons that have nothing to do with the middleware: a wrong sign in a transform, a driver reporting units it does not actually use, a gripper that was never going to hold that object. ROS 2 has an answer, or at least a well-worn discussion thread, for a great many of those. The decision is not which software is better. It is which problem you have.

If the hardest part of your robot is perception, planning, or talking to hardware somebody else already wrote a driver for, the ecosystem wins outright and the argument is over. If the hardest part is that a number computed on one core has to be acted on by another before the next tick, no ecosystem helps you, because that is a property of how messages move rather than of which packages exist. When a month of commits is about the plumbing rather than about the robot, the plumbing has become the project.

What does robotics middleware actually do for a robot?

Middleware moves a number from the code that produced it to the code that needs it, and decides when that happens. A robot is not one program. One process talks to the lidar, another estimates where the robot is, another decides where to go, another drives the motors, and something has to connect them without every author needing to know every other author's internals.

So middleware gives you named streams anyone can publish to or read from, a way to start and stop the whole set together, a way to record everything that happened and replay it on your laptop afterwards, and a way to look inside a running system without stopping it. Replaying a failing run at your desk often decides whether a bug takes a week or a month.

Middleware also decides something quieter: whether a message is copied, converted into a neutral format and rebuilt, or simply handed over. That choice is invisible while the robot is slow, and becomes the entire story once it is not.

What are the real options for building a robot today?

There are four realistic answers: ROS 2, a shared-memory middleware, a commercial real-time framework, or no middleware at all. ROS 2 is the default and deserves to be: it brings navigation, mapping, visualisation, recording, simulator integration and a large body of vendor-written drivers, and it moves messages between processes through DDS, which converts each message into a neutral format on the way out and rebuilds it on the way in. HORUS is the open-source example of the second answer, a real-time middleware in which Rust, Python and C++ all share the same shared-memory ring buffers, so messages between processes on one machine are not serialised at all. It is Apache-2.0, it is validated in simulation, and it is not a full ROS replacement for every project.

A commercial real-time framework buys a support contract and paperwork an auditor will accept, at the cost of money and a closed toolchain. No middleware at all, meaning one program and ordinary function calls, is genuinely right for small robots and is chosen far too rarely.

How do the main options compare side by side?

Find the row whose last column describes your situation, and cross it out. What survives is usually one or two.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
ROS 2Teams needing maps, navigation and vendor drivers that already existLinux, C++ or Python, packages and launch filesYour hardest problem sits above the transportYou are tuning transport settings instead of writing robot code
HORUSBuilders whose control loop misses its deadline on one machineRust, Python or C++, and how your processes are splitSeveral processes on one machine must agree quicklyYou need a mature navigation or mapping package today
A network-first transportTeams moving data between machines, robots and sitesNetworking, and how to design your own message contractsThe hard part is the link, not the loopYou expected batteries included
A commercial real-time frameworkProgrammes with a certification requirement and a budgetYour safety standard and your vendor's toolchainSomebody external will read your paperworkYou are a small team paying with your own money
Firmware on a microcontrollerPeople whose tight loop belongs beside the motorEmbedded C, interrupts and your board's timersThe loop never needs a camera or a mapYou need camera-class perception in the same loop
One program, no middlewareFirst robots and single-purpose machinesOne language and one loopEverything fits in one process and one headTwo people work on it, or runs must be replayed

Notice how little of that table is about timing. Most of it is about people and what they already know.

What do most teams try first, and why does it stop working?

Most teams first merge their nodes into one process, and it works until the robot needs two languages or two machines. The sequence is almost always the same. Composition first, so messages stop crossing a process boundary. Then thread pinning and priorities. Then switching off whatever was costing something, with logging usually the first casualty. Then a home-grown shared-memory patch for the one topic that hurts most. Then somebody proposes dropping Python from the hot path, and the person who wrote the estimator in Python objects, correctly.

Each step buys real headroom, and each costs the thing you liked about separate nodes: you can no longer restart one part without restarting everything, one crash takes the whole robot down instead of one behaviour, and shared state creeps in that nobody intended. Two years later, the architecture nobody chose is the architecture you have.

The signal is not that the workarounds failed. It is what they were about. When the fix list stops mentioning the robot, you have changed projects without noticing.

Are you a solo builder, a research group, or a product team?

Solo builders and product teams should usually make opposite choices here. If you are alone, the ecosystem is your team. Every package you do not have to write is a colleague you do not have, and ROS 2 has more of those than anything else. Pick it unless building the plumbing is genuinely the part you enjoy, in which case be honest that you are choosing a hobby rather than a shortcut.

A research group has a different constraint: reviewers and successors have to reproduce what you did. The exception is when the timing itself is your contribution, because then the plumbing is the paper.

A product team carries the maintenance. Every dependency is code somebody will debug at three in the morning during a customer trial, so the question is whose source you can read. Product teams usually start on ROS 2 and carve out one timing-critical part later, which is the sane order. If you are earlier than that and building something small, the case for skipping the heavy stack entirely is worth reading first.

What should you build on for the hardware you already have?

The board you have decides more than the middleware you like. On a single Linux board with a handful of cores, where several processes share one machine, how messages cross between processes is the thing most under your control, and it is also where ROS 2 stays perfectly comfortable as long as your loop is unhurried. That is the interesting middle, and it is where this whole argument lives.

On a microcontroller with no Linux, neither side of the comparison is your answer. You want firmware on the small board and a Linux process talking to it over a wire, and the design question is what belongs on each side.

On a robot spread across several machines, a rover plus a base station or two compute boxes in one chassis, the hard part is the network, and shared memory does nothing across a cable. That limit is real, not a footnote. If your loop mostly fuses streams from several sensors, the shape of a sensor fusion pipeline says more about your bottleneck than any transport comparison will.

How much time do you have before something has to work?

If you have weeks, use ROS 2; if you have a year and a hard timing target, the choice is genuinely open. Three weeks to a demo is not a middleware decision at all. Pick whatever has the most existing packages for the thing you are demonstrating, accept the overhead, and get the robot moving. A demo that exists beats an architecture that is correct.

A quarter changes things slightly: you can afford to build one moving part yourself, and it is worth doing if the demo is about motion quality, which an audience notices without being told to look.

A year to a product changes things completely, because the dominant cost is not the first month. It is the third year of owning what you chose: the licence terms when your company is acquired, the community when your error message returns no results, whether you can read the source when it misbehaves. A team picks the quick-to-demo option, ships the demo, and the demo quietly becomes the product without anyone deciding that it should.

How much systems programming does your team actually know?

You do not need to write Rust to make this choice, but somebody should be comfortable reading it. Bindings mean the day-to-day work happens in Python, and for most robot code, reading a sensor, doing some maths, publishing a result, that is the right amount of ceremony. The comparison of a plain Python node against its ROS 2 equivalent is a fair picture of what daily authoring feels like on each side.

The skill question bites somewhere else. When something goes wrong below your own code, somebody has to open the middleware source. With ROS 2 that source is large and C++, but the community is big enough that your exact error message has probably already been posted and answered. With a smaller project you are more often the first person to hit a given bug, which is fine if your team reads code and painful if it does not.

Skill also changes what the word easy means. A team fluent in Rust finds the compiler's complaints cheaper than a memory bug found on the robot.

What do you give up if you move off ROS 2?

You give up the packages, the tools and the answers, and that is most of what ROS 2 is. Concretely: navigation and mapping stacks that took years to get right, visualisation you did not write, recording and replay with tools that already read the format, simulator integrations, and vendor drivers shipped as packages by the company that sold you the sensor. Also the tutorials, the conference talks, the university courses, and the forum answer that matches the string in your terminal.

You give up hiring leverage too. People arrive already knowing ROS 2, and job listings that name it get read. Customers, partners and reviewers recognise it, which is worth something in conversations that have nothing to do with engineering.

A smaller project means a smaller ecosystem, and no amount of good engineering inside a project substitutes for the thousand packages outside it. So the test is strict: if you cannot name in one sentence the specific problem you are buying with that trade, do not make the trade.

When is ROS 2 the better choice?

ROS 2 is the better choice for most robots, and HORUS is not the answer when what you need is an ecosystem rather than a transport. If your robot must build a map of a building and find its way around it, use the navigation stack that exists. If your sensor vendor ships a driver as a package, use it rather than writing your own from a datasheet. If you are doing research that others must reproduce, use the shared vocabulary. If you are teaching, use the thing with the textbooks.

There are quieter cases too. Multi-machine robots, where the network is the constraint and no local trick touches it. Robots that are simply not in a hurry, where message timing has never once appeared in a bug report, which is most robots. Teams that need simulator integration this month. And the most common case of all: a team already productive on ROS 2 whose complaints, examined honestly, are about their own code. A migration will not fix your controller, your calibration, or your mechanical backlash.

Is this only about making messages arrive sooner?

No, and here is why: what you feel in the robot is not delay, it is variation. A machine that is consistently late can be compensated. A machine that is sometimes late cannot, because there is nothing stable to correct for. When copying and format conversion sit in the path, the cost of each message changes with its size, with memory pressure, and with whatever else the computer decided to do that second. That variation is what shows up as a wobble in a line that should be straight, or an arm that stops in a slightly different place each time.

Taking the conversion step out removes a source of variation, not only of delay. It also changes what you are allowed to build. If crossing a process boundary is cheap, you can keep programs separate instead of merging them, which means a crash stays local and one part can be restarted without the rest. That is an architecture benefit people routinely mistake for a timing benefit.

Do you have to rewrite your whole stack to try something else?

Partly, but not the way you think: the code rewrite is small and the relearning is large. Node bodies port over quickly, because a node body is mostly your algorithm, and your algorithm does not care how the bytes arrived. Read a sensor, run a filter, publish a result. That part is an afternoon.

What does not port is everything around it. Launch and configuration. Your recorded runs and the tools that read them. Your visualisation. Your test rig and continuous integration. And the largest one, the assumptions your team holds without stating them: how topics are named, what happens on startup, who owns which parameter.

So the path that works is boring. Keep the existing stack for everything that needs packages, move only the loop that is actually missing its deadline, and bridge at the boundary. Teams who do this end up with a small fast core and a large familiar periphery, and most never perform the full migration they thought they were starting.

How do you decide without spending a month on it?

Decide by writing down, in one sentence, the bug you are trying to kill, before you compare anything. If it reads "our robot cannot reliably find its way around the room", the answer is a navigation stack, and therefore the ecosystem. If it reads "our arm overshoots whenever the perception process gets busy", the answer is about how and when messages move, and no package supplies it.

Then run one honest week. Take your worst loop, build only that, on the hardware you will ship, in the languages your team writes, and look at the motion with your eyes. Does the stutter go away? Does the arm stop in the same place every time? Never evaluate a message bus in isolation on a spare laptop, because the answer you get will be true and irrelevant.

Test the unglamorous things too. Can you record a run and replay it? Can a new teammate build it from a clean checkout? What happens when one process is killed mid-motion?

Here is the whole decision in six lines.

The HORUS Fit Framework compresses all of that into five axes: ecosystem size, setup effort, team size fit, deployment target, and licence. Score every option on all five, and pick the one that is not red on the axis you cannot afford to lose.

If timing is the question you keep circling back to, put HORUS on your reading list rather than on your calendar: star it so it is in your list when you start building.

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