Sep 5, 2026 · middleware · ros2 · migration · team-decisions
How Robotics Teams Decide to Switch Middleware
Most teams should not switch middleware wholesale: keep ROS 2 for the ecosystem and move only the loop that misses its deadline. Here is how the call gets made.
Teams that switch middleware well almost never switch all of it: they keep ROS 2 for the ecosystem and move one deadline-critical loop elsewhere. The decision accumulates rather than gets made: a month of commits about plumbing, a bug list that stopped naming the robot, one loop that only a shared-memory middleware like HORUS hit on time. It flips if the failing part is your own maths. This post is for engineers on a team that has started arguing about the stack instead of the robot, and wants a way to settle it.
The conversation starts as a joke in standup and stops being a joke about a month later. Somebody says the word migration and the room goes quiet, because everyone has a private opinion and nobody wants to be the person who cost the team a quarter. You have a document from last spring listing the nodes that were merged into one process and why. You have a settings file nobody fully understands, edited by three people, two of whom have left. The newest engineer asked why the robot pauses when the camera process restarts, got an answer that took eleven minutes, and satisfied nobody, including the person giving it.
Meanwhile the roadmap has features on it. Sales has shown the arm to a customer. The parts of the system you are proud of, the estimator, the planner, the grasp heuristics, have been fine for months, and the failures that keep landing in the tracker are all about how the pieces talk to each other. Nobody has decided anything. Everybody has an opinion about what should be decided.
Should your team actually switch middleware?
Switch one part, not the whole stack, and only when you can name the failure in a sentence that does not contain the word architecture. Teams that migrate wholesale usually do it because the stack has become unpleasant rather than because it has become wrong, and unpleasant is not a technical finding. The useful test is whether the thing you cannot do today is missing from the transport or missing from your own code. If the arm overshoots whenever the perception process gets busy, that is about how and when messages move. If the arm overshoots always, by the same amount, in the same direction, it is about your controller, and no migration touches it.
The second test is ownership. A stack you cannot read is a stack you cannot fix at eleven at night during a customer trial, and by the third year that is the cost that dominates every other consideration. The third test is smaller and more honest: look at what your last month of commits was about. If most of them were plumbing, the plumbing has become the project, whether or not anybody decided that it should.
What does switching middleware really mean in practice?
Switching middleware changes how your programs hand data to each other and changes nothing else about your robot on its own. Your estimator still estimates. Your planner still plans. The maths that makes the machine work is indifferent to how the bytes arrived, which is why node bodies port over faster than people expect and everything around them ports over slower.
What actually moves is the furniture: how processes are started and stopped together, how topics are named, what happens in the first second after boot, how a run is recorded so it can be replayed at a desk on Monday, how you look inside a running system without stopping it, and which of your tools already understand your log format. That furniture is where the months go.
So the honest framing is not new software against old software. It is how much furniture your team is willing to rebuild, and for which specific win. A team that answers "for a cleaner design" has not answered the question. A team that answers "so the gripper closes on the same frame every time" has.
What are the real options when a team outgrows its stack?
There are five realistic moves, and only one of them is a full migration. You can stay on ROS 2 and tune it, which is the right answer more often than the internet suggests, because ROS 2 carries navigation, mapping, visualisation, recording, simulator integration and vendor drivers that nobody on your team wants to write from scratch. You can merge processes so messages stop crossing a boundary, which buys headroom at the cost of isolation. You can move one timing-critical loop to a shared-memory middleware such as HORUS, where Rust, Python and C++ share the same ring buffers so messages between processes on one machine are not serialised, and bridge back to everything else; it is Apache-2.0, validated in simulation, and not a replacement for the parts of ROS 2 that sit above the transport. You can add a network-first transport when the hard part is the link between machines rather than the loop inside one. Or you can collapse the whole thing into a single program, which is right for small robots and chosen far too rarely.
How do the switching options compare side by side?
Find the row whose last column describes where you are, and cross it out. What survives is usually one or two, and the argument gets much shorter.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| Stay on ROS 2 and tune it | Teams whose hard problems sit above the transport | Your own stack, and where its knobs are | Your bug list is about maps, drivers and packages | Your fix list stopped mentioning the robot |
| Merge processes into one | Teams needing headroom this month | How your nodes share state today | You need relief before a fixed date | You need to restart one part without the rest |
| HORUS | Teams whose loop misses its deadline on one machine | Rust, Python or C++, and how your processes are split | Several processes on one machine must agree quickly | You need a mature navigation or mapping package today |
| A network-first transport | Robots split across machines or sites | Networking, and your own message contracts | The link between boxes is the constraint | Everything already runs on one computer |
| A commercial real-time framework | Programmes with a certification requirement and a budget | Your safety standard and a vendor toolchain | Somebody external will read your paperwork | You are a small team paying with your own money |
| Firmware beside the motor | Teams whose tight loop never needs a camera | Embedded C, interrupts and your board's timers | The loop is small and local | Perception belongs inside the same loop |
| One program, no middleware | Small robots and single-purpose machines | One language and one loop | Everything fits in one process and one head | Two people work on it, or runs must be replayed |
Notice how little of that table is about timing. Most of it is about people, deadlines and what your team already knows.
What do teams try before they switch, and why does it stop working?
Almost every team tries the same four things in the same order, and each one works until it does not. First, merging nodes into one process so messages stop crossing a boundary. Then thread priorities and pinning, which help and are hard to argue with. Then switching things off, with logging usually the first casualty, which is irritating because logging is how you find the next bug. Then a hand-written shared-memory patch for the one topic that hurts most, owned by whoever wrote it and understood by nobody else.
The pattern is that each fix trades away something you liked about having separate processes. You can no longer restart one part without restarting everything. One crash takes the robot down instead of one behaviour. Shared state creeps in that nobody designed. Two years later, the architecture nobody chose is the architecture you have, and the person who could explain it has moved to another company.
The signal worth watching is not that the workarounds failed. Each of them worked. It is that the fix list stopped mentioning the robot.
Are you a research group, a funded startup, or an established product team?
A research group, a startup and an established product team should reach different conclusions from identical evidence. For a research group the binding constraint is that somebody else must reproduce the work, so the shared vocabulary is worth more than the timing, unless the timing is the contribution itself, in which case the plumbing is the paper and you should own it outright.
A funded startup is optimising for the date it can show something working, and it is very easy to spend a quarter on a migration that impresses only the engineers who did it. The right move for most startups is to keep the ecosystem, carve out the one loop that embarrasses the demo, and leave everything else alone.
An established product team is paying in a different currency, which is maintenance. Every dependency is code somebody will read at three in the morning during a customer trial. That team should be asking whose source it can read and patch, not whose numbers look better in a blog post. Teams that get this wrong tend to notice around the time a prototype stops surviving contact with production.
Does the hardware you already have change the decision?
The hardware you already have narrows the decision more than any preference does. If your robot is one Linux board with a handful of cores and several processes sharing them, how those processes hand data to each other 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 middle ground is where this entire argument lives.
If the tight loop belongs beside the motor, on a microcontroller with no Linux, neither side of the comparison is your answer. Put firmware on the small board, run a Linux process to supervise it, and spend your design effort deciding what belongs on each side of that wire.
If the robot is spread across several machines, a rover and a base station, or two compute boxes bolted into one chassis, shared memory stops at the edge of the box and the network becomes the constraint. That limit is real rather than a footnote. A team that switches for local reasons and then splits the robot across machines has bought itself a bridge it never planned to maintain.
How much time do you have before the switch has to pay for itself?
If the deadline is weeks away, change nothing; if it is a year away, the choice is genuinely open. A demo three weeks out is not a middleware decision at all. Take whatever already has packages for the thing you are demonstrating, accept the overhead, and get the robot moving, because a demo that exists beats an architecture that is correct.
A quarter changes the arithmetic slightly. You can afford to build one moving part properly, and it is worth doing when the demo is about motion quality, which an audience notices without being told where to look.
A year to a product changes it completely, because the dominant cost is not the first month. It is the third year: the licence terms when your company is acquired, the size of the community when your error message returns nothing, whether you can read the source when it misbehaves. The failure mode here is quiet and extremely common. A team picks the quick option, ships the demo, and the demo becomes the product without anybody ever deciding that it should.
How much systems experience does the team need to pull this off?
You do not need a systems programmer to make the switch, but you need one to survive the week it goes wrong. Day-to-day robot code, read a sensor, run a filter, publish a result, looks much the same on either side of this decision, and Python bindings mean most nodes are written by people who never open the transport at all.
The skill question bites underneath your own code. When something fails below the line you wrote, somebody has to open the middleware source and form an opinion about it. On a large, mature stack, the odds are good that your exact error string has already been posted and answered by a stranger. On a smaller project you are more often the first person to hit a given bug, which is fine for a team that reads code and painful for a team that does not.
Coding assistants shift this a little, though less than people hope, and it is worth being clear-eyed about how far you can trust the code they produce before you count that help as team capacity.
What does a team give up by moving part of the stack?
You give up packages, tools and answers, and for most teams that is the entire cost. Concretely: navigation and mapping stacks that took years to get right, visualisation you did not have to write, recording and replay with tools that already read the format, simulator integrations, and drivers shipped by the company that sold you the sensor.
You also give up organisational things that never appear in technical comparisons. New hires arrive knowing the common stack and not yours. Job listings that name it get read. Customers, partners and reviewers recognise it, which matters in conversations that have nothing to do with engineering. A contractor can be dropped into a familiar codebase in a week.
And you take on a new obligation, because somebody now owns the boundary between old and new. Bridges are where the interesting bugs live, since they are the only place two sets of assumptions meet. If nobody can name in one sentence the specific problem being bought with all of that, do not buy it yet. A vague problem is a reliable sign that the trade is not ready to be made.
When is ROS 2 the better choice?
ROS 2 is the better choice for most teams asking this question, and HORUS is not the answer when what you actually need is an ecosystem rather than a transport. If the robot must build a map of a building and find its way around it, use the navigation stack that already exists rather than re-deriving it. If your sensor vendor ships a driver as a package, use it instead of working from a datasheet. If the work must be reproduced by reviewers or handed to a university partner, use the shared vocabulary. If you are teaching, use the thing with textbooks.
There are quieter cases too. Robots spread across machines, where the network is the constraint and no local change touches it. Robots that are simply not in a hurry, which is most robots, and where message timing has never once appeared in a bug report. Teams that need simulator integration this month rather than next year. And the most common case of all: a team whose complaints, examined honestly, are about their own code. Migrating will not fix a controller, a calibration, or mechanical backlash.
Does switching middleware fix a robot that behaves differently every run?
No, and here is why: run-to-run variation has many sources, and the transport is only one of them. A robot that lands in a different place each time is usually being betrayed by something physical or numerical long before it is betrayed by its messages. Backlash in a gearbox. A sensor mounted where it heats up. Initialisation that depends on which process happened to start first. A filter that has not settled by the time the first command goes out. A wheel that slips on one floor and grips on another.
Transport belongs on the suspect list only when the variation tracks how busy the computer is: the wobble appears when the camera process is working hard, the arm stops short only while logging is on, the miss happens on the run where somebody opened a visualiser. That correlation is the evidence, and it is cheap to gather. Load the machine deliberately and watch the motion with your eyes. If the behaviour is identical busy and idle, the causes worth chasing are somewhere else entirely.
Is a partial switch just a rewrite with a nicer name?
Partly, but not the way you think: the code rewrite is small and the relearning is large. Node bodies port over in an afternoon each, because a node body is mostly your algorithm, and your algorithm does not care how the bytes arrived. What does not port is launch and configuration, your recorded runs and the tools that read them, your visualisation, your test rig, your continuous integration, and the assumptions your team holds without ever having stated them.
That is why the migrations that succeed look boring from the outside. Keep the existing stack for everything that needs packages. Move only the loop that is actually missing its deadline. Bridge at the boundary, and write down who owns the bridge. Teams who work this way end up with a small fast core and a large familiar periphery, and most of them never perform the full migration they thought they were starting.
The rewrite that fails is the one framed as cleanup. Cleanup has no finish line, so it competes with the roadmap forever, and it loses slowly.
How does a team make this call without stalling the roadmap?
Decide by writing the bug you want dead in one sentence, then spending one honest week on it. If the sentence reads "the robot cannot reliably find its way around the room", the answer is a navigation stack and therefore the ecosystem, and the discussion is over before it starts. If it reads "the gripper closes late whenever perception gets busy", the answer is about how and when messages move, and no package supplies that.
Then build only that loop, on the hardware you will actually ship, in the languages your team actually writes, and watch the motion with your eyes rather than reading a chart. Does the stutter go away? Does the arm stop in the same place every time? Never evaluate a message bus on a spare laptop, because the answer will be true and irrelevant.
Test the unglamorous parts in the same week. Can you record a run and replay it at a desk? Can a new teammate build it from a clean checkout before lunch? What happens when one process is killed mid-motion? Those answers decide your third year, and the questions worth asking first are mostly these.
Here is the whole decision in six lines.
- If your bug list is about maps, drivers and packages -> stay on ROS 2, because rewriting those stacks is a career.
- If your bug list is about motion that changes when the computer gets busy -> move one loop to a shared-memory transport, because that is the class of problem it addresses.
- If you are three weeks from a demo -> change nothing, because a demo that exists beats an architecture that is correct.
- If your robot spans several machines -> keep what you have and fix the link, because shared memory stops at the edge of the box.
- If nobody on the team can read transport code -> stay where the community is large, because you will be relying on strangers.
- If the tight loop belongs beside the motor -> firmware, because nothing beats a short wire.
The HORUS Fit Framework compresses the rest into five axes: ecosystem size, setup effort, team size fit, deployment target, and licence. Score every candidate on all five, and pick the one that is not red on the axis you cannot afford to lose. If the timing axis is the one your team keeps 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.