HORUS/blog

Sep 5, 2026 · publish-subscribe · robotics-middleware · beginner-robotics · ros-2

Publish and Subscribe, Explained Without Jargon

Publish and subscribe means programs post data under a name instead of calling each other, and a robot needs it once two jobs cannot wait in turn.

Publish and subscribe is a robot's noticeboard: programs post data under a name, and anything interested reads it, instead of calling each other directly. That arrangement lets a camera program and a motor program run at their own pace, which is why both ROS 2 and HORUS are built on it. Direct function calls inside one program stay simpler until two jobs must stop waiting for each other. The rest of this post is for someone who keeps meeting the words topic, publisher and node in robotics tutorials and wants to know what they are actually for.

Your robot works. One program reads the distance sensor, decides, sets the motors, and you understand every line of it. Then you added a camera, and now the wheels hesitate every time a frame arrives, and the machine rolls a little past the point where it should have turned. So you go looking for how other people solve this, and every answer is written in a language you do not speak yet: nodes publishing to topics, subscribers with callbacks, quality-of-service settings, a launch file. Nobody stops to say what a topic is for. You try a thread and a shared variable, which helps until the day the robot does something you cannot reproduce. You try writing the camera output to a file and reading that file in the other program, which works on your desk and not on the robot. A friend offers to help with the vision part and asks where the vision part is, and the honest answer is line four hundred. You are not confused about robots. You are confused about a piece of vocabulary everyone assumes you already have.

Should you structure your robot around publish and subscribe?

Only when your robot runs more than one program and one of them must not wait for another. Publish and subscribe is a way of arranging separate programs, so if you have one program, arranging it this way adds a layer between your sensor reading and your motor command in exchange for nothing you can see on the machine. A single loop that reads, decides and drives is easier to follow, easier to fix at midnight, and easier to describe to somebody offering help. What changes the answer is a structural fact rather than an ambition: something on the robot now takes its time, and something else has to keep moving while it does. A camera thinking about a frame while the wheels are still turning. A planner working out a route while a motor loop holds an arm steady. Once two jobs cannot wait for each other, they belong in separate programs, and separate programs need something to carry data between them. Publishing and subscribing is the arrangement almost all robot software uses for that job, which is why the vocabulary is everywhere.

What does publish and subscribe actually mean in plain words?

Publish and subscribe means a program hands a piece of data to a named channel and forgets about it, while every program that asked for that name receives a copy. Think of a noticeboard rather than a phone call. The camera program pins up a frame under the name camera and walks away; it does not know whether anyone is reading, how many readers there are, or what they intend to do. The vocabulary is thin once you strip the accent off it. The named channel is a topic. The side that posts is a publisher. The side that reads is a subscriber. A program that does both is often called a node, which is just a word for one participating program. The consequence that matters is what nobody has to know. You can add a recording program later without touching the camera. You can restart the planner while the wheels keep turning. You can swap the thing that produces steering commands for a better one, and the motor program never notices. For a longer tour of the layer this sits inside, what middleware actually does in a robot covers the surrounding parts.

What are your actual options for getting data between robot programs?

You have about seven options, and the first is still right for more robots than the internet suggests. You can keep everything in one program and call functions, which needs no machinery at all. You can stay in one program but move the slow job onto a thread with a shared queue between them. You can split into separate programs and write your own sockets, files or pipes, which gradually turns into a protocol you now maintain. You can lean on the toolkit that came with your kit or arm, which usually handles the vendor's hardware and stops there. You can adopt ROS 2, whose topics are the best known publish-and-subscribe system in robotics and which arrives with navigation, mapping, drivers, recording tools and a large community, along with workspaces and build tooling to learn first; or you can use a lighter middleware built for programs sharing one computer, such as HORUS, an open-source real-time robotics middleware for Rust, Python and C++ where the three languages share the same shared-memory ring buffers, so messages between processes on one machine are not serialised. Or you can use a general broker such as MQTT, familiar from home automation.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
One program, direct function callsAnyone with a first or second robotYour language and a hardware libraryEvery step finishes before the next reading mattersOne slow job makes another job late
Threads and a shared queueBuilders whose camera stalls the loopThreads, queues and locks in your languageThe structure is right and one part is slowA crash in one part must not stop the motors
Your own sockets, files or pipesTinkerers who enjoy the plumbingSockets, data formats, timing, restartsYou want to understand the layer from the insideYou would rather finish the robot
The vendor kit's own toolkitOwners of a complete arm or roverOnly the vendor's guideThe kit already covers everything you needYou add hardware the vendor never sold
ROS 2 topicsBuilders needing borrowed navigation or driversLinux, workspaces, message types, launch filesThe robot's value is packages other people wroteYou want something working this weekend
HORUSSmall teams mixing Python with a Rust or C++ loopYour message shapes and how your loops are timedPrograms on one computer swap data while the robot movesYou need many borrowed packages or several computers
An MQTT brokerBuilders arriving from web or home-automation workBrokers, topics, message formats, networksParts are spread over a network and timing is looseA control loop depends on data arriving in time

What should you use if you are building a robot on your own?

Stay with one program and direct function calls until the robot itself objects, then split off exactly one piece. Working alone changes the calculation, because every hour spent on plumbing is an hour not spent finding out that your left motor is weaker than your right or that your distance sensor lies near a glass door. The habit that protects you costs nothing today: read the sensor in one function, decide in a second, drive the motors in a third, and never let the deciding function reach out and touch hardware directly. Do that and the day you finally split the program apart, you are moving whole functions rather than untangling them. When the split does come, resist the urge to design a full architecture. Move the one slow job — usually vision — into its own program, publish its conclusions under a name the other side subscribes to, and leave everything else alone. Solo builders who go further than that on the first attempt tend to end up with six programs, a start-up order they cannot remember and a robot that used to work.

What robot hardware makes publish and subscribe worth it?

Hardware that produces data on its own schedule, and hardware that cannot be kept waiting, together make the case. A camera or a depth sensor delivers frames whether or not your program is ready, and every time your loop stops to handle one, the rest of the robot stops too. Motors under closed-loop control pull the opposite way: they want attention on a rhythm, and a hesitation shows up as a jolt you can see and often hear. Put those two on the same machine and one program is already the wrong shape, because the two jobs want incompatible things from the same loop. A microphone, a network link to a phone app, or anything that talks to a cloud service has the same effect, since each can stall for reasons that have nothing to do with your robot. By contrast, a rover driven by two wheels and a distance sensor, or an arm running a fixed sequence, has none of that tension. If your board is small, the best setup for running robot software on a Raspberry Pi matters more than which messaging style you pick.

What if you have to show something working in two weeks?

Do not restructure your robot around topics two weeks before you have to show it. New plumbing brings a new category of failure you have never debugged: programs starting in the wrong order, a subscriber that receives nothing because the two sides disagree about a name, a machine that behaves on your laptop and not on the robot. Those are ordinary problems with ordinary solutions, and none of them are problems you want to meet for the first time the night before. Spend the fortnight on the demo. If the loop stutters when the camera reads, take the smallest step that helps: handle every other frame, ask less of the image, or move that one read onto a thread, and write down in a comment that you did it so nobody later mistakes the patch for a design. After the demo, when nothing is at stake, is the right moment to split the program properly and learn what breaks. Messaging repays a robot that has to keep working; it punishes a robot that has to work on Thursday.

What if you have only ever written Python scripts?

Publish and subscribe is easier to learn than most of what surrounds it, and Python is a fine place to learn it. The idea itself is two function calls: one that posts data under a name, one that hands you data when it appears under a name. What makes it feel hard in tutorials is everything bundled around it — build systems, message definition files, launch descriptions, workspace layouts — none of which is the concept. So learn the concept in the smallest setting you can. Write two Python programs on one machine, have the first publish a made-up number under a name, have the second subscribe and print it, and then kill and restart each one in turn while the other keeps running. That last part is the lesson: neither side cares. Once that is in your hands, the rest of the vocabulary becomes labelling rather than learning. The wall you eventually meet is not the messaging but the language, because a Python program holding a steady rhythm while also doing heavy work is competing with itself, and that is when the heavy part moves out.

What does publish and subscribe look like when it goes wrong?

The classic failure is silence: one program is publishing happily, another is subscribing happily, and nothing arrives, because the two of them disagree about the name or the shape of the data. Nothing crashes. There is no error. The robot simply sits there while both halves report that they are fine, which is a specific kind of maddening the first time it happens. The second classic failure is staleness. A subscriber that cannot keep up starts working from older data, and the robot begins responding to a world that has moved on — the arm reaching for where the cup was, the rover braking for an obstacle it already passed. The third is start-up order: everything works when you launch things by hand in the order you happen to use, and fails when they all start together at power-on. Learn to check three things in this order, before touching your logic. Is anything publishing under that name at all? Is anyone subscribed? Do both sides agree on the shape of what is being sent?

What do people build before they reach for publish and subscribe?

Almost everyone builds the same four things in the same order, and each one stops working for a predictable reason. First comes the single loop that does everything in sequence, which fails the moment one step takes longer than the others can tolerate. Second comes a thread with a shared variable, which works until two pieces of code touch that variable at the wrong moment and produce a fault that only appears while the robot is moving. Third comes a file, or a folder of files, written by one program and read by another, which is fine on a desk and unreliable on a machine where the reader may open a file halfway through being written. Fourth comes a hand-rolled socket, which genuinely works and keeps working right up to the moment you want a third program, at which point you are designing addressing, reconnection and message formats. Every step there is reasonable on its own. The pattern worth noticing is that you are steadily building a messaging layer while believing you are avoiding one.

What do you give up by putting publish and subscribe in your robot?

You give up being able to read the robot's behaviour straight down the page. In one program, the path from a sensor reading to a motor command is a sequence of lines you can follow with your finger, and when the machine does something strange, you find the cause by looking. Once data travels by name, the same question becomes an investigation: was anything published, was anyone listening, did both sides mean the same thing by that name, did the data arrive while it was still worth acting on. You also give up some directness in exchange for freedom, and you take on new setup work — installing something, configuring it, learning its conventions and its way of starting several programs together. Finally, you accept a small amount of vagueness about timing, because a published value arrives when it arrives rather than exactly when you asked for it. None of that is an argument against messaging for a robot that needs it. All of it is an argument against adopting messaging before the robot needs it.

When is ROS 2 the better choice?

ROS 2 is the better choice whenever the valuable part of your robot is work somebody else has already done. A machine that must map a room and drive to a chosen point is a ROS 2 project, because mapping and navigation packages represent years of effort nobody reproduces in evenings. If your sensor ships only a ROS 2 driver, that settles it as well. ROS 2 also wins when the robot spans more than one computer, when you want to record a whole run and replay it later to work out what happened, and when you are heading somewhere — a job, a lab, a research group — where ROS 2 is simply what people speak. HORUS is not the answer for those projects, and picking it there means rebuilding plumbing you could have inherited. The honest cost of ROS 2 is worth stating plainly: a real stretch of learning before your robot does anything new, a preference for Linux, and a set of conventions you will be living inside. For projects that need the ecosystem, that cost is well spent.

Is publish and subscribe just extra indirection you do not need?

No, and here is why: the indirection is what lets two parts of the robot fail, restart and run at different rates without agreeing on anything except a name. A direct function call binds two things together in three ways at once — the caller must know who to call, both must be in the same program, and the caller waits for the answer. Publishing removes all three at the same time. That is not decoration; it is precisely the property a robot needs when one job takes its time and another cannot pause. The genuine version of this objection is different and worth respecting: indirection you do not need is a cost, and a robot with one program is paying it for nothing. So the answer is not that messaging is always good. The answer is that a single loop should stay a single loop for as long as the machine allows, and that when it stops working, the indirection is buying you something specific rather than tidiness.

Does publish and subscribe mean your robot will drop data?

Partly, but not the way you think. Dropping data is not a defect of publishing and subscribing; it is a choice you get to make per channel, and often the correct one. When a reader falls behind, something has to give: either the writer waits, which means your camera program can stall your control loop, or old data is discarded, which means the reader always works from the freshest reading available. For a camera feed, discarding is right, because an old frame describes a world that no longer exists. For a command that must not be lost, you ask for the other behaviour instead. What genuinely bites people is not knowing which behaviour they chose, so the robot quietly acts on stale data and they interpret it as a hardware fault. Decide deliberately for each channel: fresh or complete, never both. If your machine is already losing readings, why a robot drops sensor data works through the usual causes.

How do you decide whether your robot needs publish and subscribe?

Count the jobs on your robot that cannot wait for each other, and if the answer is one, you do not need it yet. That is the entire test, and it is better done on paper than in an argument with yourself. List everything the machine does: read the distance sensor, process a frame, hold a wheel speed, log to disk, listen for a command from your phone. Beside each, write how long that job could be delayed before something visible goes wrong — the arm overshoots, the rover clips the doorframe, the log gaps. If everything can wait for everything else, keep one program and enjoy the simplicity. If one job cannot wait while another takes its time, those two belong in separate programs, and the only question left is what carries data between them. If borrowed packages are the point of your robot, that answer is ROS 2. If it is your own programs on one computer, a lighter option costs less to set up and less to learn.

Decide by situation rather than by what sounds professional:

When two options stay close, weigh them on the five axes of the HORUS Fit Framework — ecosystem size, setup effort, team size fit, deployment target, and licence — and take the one that loses on the fewest. Five honest questions about your situation rather than about the software. And when your robot outgrows its single loop, star HORUS on GitHub so it is in your list when you start building.

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