HORUS/blog

Sep 5, 2026 · middleware · robotics-basics · ros2 · getting-started

What Does Middleware Actually Do in a Robot?

Middleware moves data between the parts of a robot and decides who waits. Start with ROS 2 for the ecosystem; look at HORUS when handoffs miss the cycle.

Middleware carries data between the parts of a robot and decides who waits on whom; ROS 2 and HORUS are two answers to that job. Once a robot does more than one thing at once, those parts must exchange data without stalling each other, and hand-written glue starts dropping messages under load. If your whole robot fits in one loop, you do not need any of this yet. The rest of this post is for someone building their first real robot who keeps hearing the word middleware and wants to know whether to care.

You had a robot that worked fine as one Python script. Then you added a camera, and the loop driving the motors started hitching every time a frame arrived. So you moved the camera into its own thread, and now the motor loop sometimes reads a position that is half written — the arm twitches, and you cannot make it happen again on purpose. You added a lock, and now the camera thread waits on the motor thread and the whole robot runs at the pace of its slowest part. Someone on a forum told you to just use ROS, and the tutorial you found spends its first hour on workspaces, build tools and a launch file before anything on the robot moves. You are not sure whether you are being sold the solution to a problem you have, or the solution to a problem that companies with twenty engineers have. What you want to know is what this layer is supposed to do for you, what actually breaks without it, and whether the right answer for your robot is a framework, a small library, or nothing at all.

Do I need middleware at all, or can I just call functions between the parts of my robot?

Call functions directly until two parts of your robot need to run at different rates, and not before. A robot that reads a sensor, computes a command and writes to a motor in one loop needs nothing else, and adding a message layer to it is pure cost. The trouble starts when one part cannot keep the other's pace: the camera takes a while to hand you a frame, the planner thinks for longer than a motor cycle lasts, the logger touches the disk. In a single loop, the slowest step sets the pace for everything, so your motor loop inherits the camera's stutter. The usual first fix is threads, and threads are where the real questions start: who owns the newest sensor reading, what a reader sees while a writer is halfway through, what happens when the consumer falls behind the producer. Middleware is a pre-built answer to those three questions. If you are still at the one-loop stage, skip it, and come back when the arm twitches for reasons you cannot reproduce.

What is robot middleware in plain terms?

Robot middleware is the delivery system between the parts of a robot: it takes data one part produced and makes it available to the parts that need it, without either side knowing where the other lives. Concretely, the camera process says here is a frame, the obstacle checker says give me the newest frame, and the middleware handles everything in between — the buffering, the handoff, the case where the checker asks before any frame exists, the case where frames pile up faster than the checker reads them. Most middleware also carries a naming scheme, so parts refer to each other by topic rather than by address, and a way to start a whole set of processes together. What middleware is not: it does not decide what your robot should do, does not run your neural network, and does not turn bad control code into good control code. Think of it as postal service rather than policy. The value it delivers is simple to state — a message posted before the deadline arrives before the deadline.

What are the actual options for moving data around a robot?

There are about five families, and most people only ever hear about one. You can write it yourself with threads and queues. You can use ROS 2, the default across robotics research and industry, and the one with the ecosystem behind it: drivers, visualisers, navigation stacks, and a hiring pool that already knows the vocabulary. You can use a general message broker such as MQTT, which suits a robot that is really a fleet of devices reporting to a server. You can use whatever SDK your arm or drone vendor shipped, often the quickest way to move one specific piece of hardware and a dead end for everything else. Or you can use a shared-memory middleware such as HORUS, open source under Apache-2.0 like ROS 2, aimed at the narrower case where several processes on one machine must trade data inside a single control cycle. Most first robots should start with the ecosystem and only leave it when something specific pushes them out.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
One loop, no middlewareFirst robots, single sensorBasic Python or C++Everything runs at one rateTwo parts need different rates
Your own threads and queuesSmall teams, fixed scopeLocks, ownership, race conditionsThe data flow is tiny and stableThe number of parts keeps growing
ROS 2Almost everyone starting outCommand line, build tools, patienceYou want drivers and tools readyThe whole robot is one process
MQTT or another brokerFleets reporting to a serverNetworking and brokersDevices sit across a networkAll the parts share one board
Vendor SDKTeams committed to one machineThe vendor's own idiomsYou ship that hardware onlyYou expect to swap hardware later
HORUSTeams whose parts share one machineRust, Python or C++, and processesHandoffs must land inside a cycleYou need a full driver ecosystem

What does it look like when a robot has no middleware and needs one?

It looks like intermittent misbehaviour that everybody blames on the hardware. The classic signs: the arm occasionally overshoots and you cannot make it do it again; the robot drives straight until you open the camera viewer, then it weaves; a sensor value reports an impossible jump about once an hour; everything is fine on your laptop and stutters on the robot. Underneath, these are nearly always data-handoff problems. Two threads touch one variable and a reader catches it mid-write. An unbounded queue lets a slow consumer build a backlog, so the controller acts on readings from the past. A lock held during a file write blocks the control thread for as long as the disk takes. The tell is that the symptom moves when you change something unrelated — add a print statement and the bug hides. That is timing, not logic, and staring at your control equations will not fix it. If the impossible jumps come from combining two sensors instead, that is a different problem: see fusing wheel odometry with an IMU.

Which option fits me if I am a student, a hobbyist, or a two-person team?

Start with ROS 2 in all three cases, because your bottleneck is not timing, it is everything else. A student needs a stack that matches the papers and the course, and a supervisor who can actually help. A hobbyist needs a driver for the parts they bought and a forum thread to read when nothing publishes. A two-person team needs to not spend one of its two people maintaining plumbing. Those are all ecosystem arguments, and the ecosystem argument beats nearly every other argument at the start of a project. The exception is the solo builder already comfortable in Rust or C++ whose robot is one machine doing one demanding thing — a balancing robot, a force-controlled gripper. That person often finds the framework's build system a bigger obstacle than the plumbing it saves. Even then the safer route is to build the thing first with whatever gets a wheel turning, and change the data layer once you can name the deadline you keep missing.

What should I use on a small single-board computer versus a bigger machine?

The size of the computer matters less than how many separate computers you have. On a single-board computer everything shares one processor and a small pool of memory, so copying and re-encoding every message between processes is felt directly — that is the setting where a shared-memory design earns its keep, and where a heavy framework can feel like it is eating the board. On a larger machine that same overhead usually vanishes into the headroom, and you should optimise for tooling instead of for the data path. If your robot has a microcontroller doing the fast loop and a Linux board doing the thinking, the more interesting question is where that boundary sits, which is really an operating-system question and is covered in do you need a real-time operating system for your robot. And if parts of your robot live on different machines joined by a network, you need a transport that speaks over the network — shared memory does not leave the machine it lives on.

What changes if I need something moving this weekend versus next year?

For a weekend, use whatever has a tutorial for your exact hardware, even a vendor SDK, and do not think about architecture at all. Deadlines that short are won by copying a working example, and every hour spent on message plumbing is an hour not spent discovering that your motor driver wants a particular wiring order. For a project measured in months, invest in the framework with the ecosystem, because the cost that will actually hurt is the driver you have to write yourself for a sensor nobody else bought. For a project measured in years, or one that ships to somebody else, start asking about the layer underneath: what happens when the machine is loaded, what your licence obligations are, whether you can still debug this once the person who wrote it has moved on. The data layer is the cheapest piece to change early and the most painful to change late, because by then every part of your robot has an opinion about it.

What if I have never written multithreaded code?

Then use something that has already made the threading decisions for you, and do not write your own message passing. The failure mode for a first-time concurrent programmer is not that the code fails to work — it usually works on the bench, which is the trap. It breaks once a week under load, in a way that looks like a loose connector, and the debugging skill that finds it is one you build over years. Any established middleware is a way of buying that experience instead of earning it. The learning curve you will actually face is a different one: the build system, the vocabulary, and the ceremony of declaring a part of your robot before it is allowed to say anything. That curve is real, and it is why so many people bounce off robotics frameworks in week one; a lighter approach to declaring a node is shown in writing a robot node in Python without the boilerplate. Pick whichever option gets a message flowing soonest, then stay in it long enough to learn what the words mean.

What do I give up by putting middleware in my robot?

You give up directness, and that costs you in debugging. A function call is easy to follow: you can step into it. A message is not — you now have a publisher, a subscriber, a topic name that has to match on both sides, and a whole class of bug where nothing crashes and nothing happens because a name is misspelled or a type gained a field. You give up some control over ordering: parts that used to run in a fixed sequence now run whenever their data arrives, which is exactly what you wanted and also means the order can differ between runs. You take on a dependency, with its release churn, its build system and its opinions about how your project should be laid out. And you add a layer that will occasionally be the real cause of a problem, so you need at least a rough picture of how it moves bytes. None of that is a reason to avoid middleware. It is a reason not to add one before your robot needs it.

When is ROS 2 the better choice?

ROS 2 is the better choice for most robots, and specifically whenever your hard problems are not timing problems. If you need a working lidar driver, a mapping stack, a simulator other people have already wired up, or a viewer that shows what the robot thinks it sees, ROS 2 hands you those on day one and nothing else comes close. If your team will grow, ROS 2 is what the people you hire already know. If you are publishing research, ROS 2 is what reviewers expect to read about. If your robot spans several machines over a network, ROS 2's transport is built for exactly that and shared memory is not. HORUS is not the answer in any of those cases and does not pretend to be — a shared-memory layer for processes on one machine is a narrow tool, not a substitute for a framework with a decade of drivers behind it. The honest split: default to ROS 2, and only look at a specialised data layer once you have a deadline you keep missing and have traced the misses to the handoff between processes.

Is middleware just overhead I could write myself in an afternoon?

No, and here is why: the afternoon version works, and the bill arrives three months later. A ring buffer between two threads really is a short piece of code, and most engineers write one at some point. What takes longer than an afternoon is everything around it — what happens when the reader is slower than the writer, whether a reader interrupted mid-read still sees a coherent value, how a process that starts late finds the buffer, what cleans up when a process dies holding a slot, and how you find out that messages are being dropped instead of losing them silently. Each of those is a day of work and a week of debugging when you get it wrong, and you will get at least one of them wrong. The second cost is social: your hand-rolled layer is undocumented, so every new person has to read it, and you become the only one who can fix it. Write your own when the data flow is small and unlikely to grow. Otherwise you pay the same price in instalments.

Does choosing a middleware lock me into it forever?

Partly, but not the way you think. The lock-in is not the message passing itself — swapping how two parts exchange a struct is a contained change that a competent team does in days. The lock-in is everything built on the framework's conventions: its message type definitions spreading through your code, its build system shaping your repository, its parameter and launch machinery baked into how the robot is configured, and every third-party driver written against its interfaces. That last one is the real anchor. A team can move its own code to a different transport in a sprint and then discover that the depth camera driver, the arm controller and the simulator bridge all assume the framework they are leaving. The defence is boring and it works: keep your control logic in plain functions that take data and return data, and let framework-specific code be a thin shell around them. Teams that do this can change the data layer later. Teams that write the control law inside a callback usually cannot.

How do I decide which one to start with?

Ask what is currently costing you the most time, and choose the option that removes that specific cost. If you cannot get a sensor to produce data at all, you have a driver problem, the framework with the most drivers wins, and the decision is already over. If you can get data but the robot behaves differently every run, you have a timing problem, and you should find out where the time goes before choosing anything — often the answer is a lock nobody knew was there. If everything works and you are simply annoyed by ceremony, that is a real cost but a small one, and changing stacks to cure annoyance usually trades it for a different annoyance. A test that works: write one sentence describing what your robot fails to do, in physical terms — the gripper closes late, the map drifts while the camera is busy — then check whether the option you are considering addresses that sentence. If you cannot write the sentence yet, you are not ready to choose, and the default is the ecosystem.

Where that leaves you, in order of how common the situation is:

When you compare candidates properly, the HORUS Fit Framework keeps the comparison honest across five axes with no numbers in them: ecosystem size, setup effort, team size fit, deployment target, and licence. A stack that wins four axes and loses the one that matters to you is still the wrong stack.

If the shared-memory case is the one you recognise, put HORUS on the shortlist before you need it — star it so it is in your list when you start building, rather than trying to recall the name on the day you are already late.

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