ROS (Robot Operating System) isn’t really an operating system — it’s a messaging framework that lets many small programs (“nodes”) talk to each other, usually spread across a robot’s different sensors, motors, and brains. The single most important pattern in ROS is publish/subscribe: one node broadcasts data on a named “topic,” and any number of other nodes can listen in, with neither side needing to know the other exists.
This practical builds exactly that: one node that shouts “hello” once a second, and one node that listens and prints what it hears. Once you’ve seen this work, you’ve seen the core idea behind almost every ROS system, robots included.
ROS is built and tested for Ubuntu Linux. It is not officially supported on macOS, and trying to install it directly on your Mac is a common source of wasted afternoons. The standard fix — what real robotics labs do on Mac-based dev machines is to run ROS inside a Docker container, and use VS Code’s “Dev Containers” extension to edit and run code inside it as if it were local.
ms-vscode-remote.remote-containers in the Extensions panel).hello_ros/) somewhere on your
Mac, and open that folder in VS Code (File > Open Folder...).Cmd+Shift+P) and run Dev Containers: Reopen in Container.Dev Container: ROS 2 Jazzy Hello World.Open a terminal in VS Code (Ctrl+`) and run ROS’s own built-in demo nodes, just to confirm the environment itself works before we touch any of
our own code:
ros2 run demo_nodes_py talker
You should see it printing Publishing: "Hello World: 0" once a second. Leave it running, open a second terminal (the + icon in the terminal
panel), and run:
ros2 run demo_nodes_py listener
You should see the listener printing I heard: "Hello World: 0" in step with the talker. Stop both with Ctrl+C — this confirmed your setup is solid, so if anything goes wrong from here, it’s the code, not the environment.
Now the two files we actually wrote: talker.py and listener.py. Open both in the VS Code editor and read through the comments first — the comments explain why each line exists, not just what it does.
In one terminal:
python3 talker.py
In a second terminal:
python3 listener.py
You should see the talker logging Publishing: "Hello, ROS! (message #N)"
once a second, and the listener logging Heard: "Hello, ROS! (message #N)"
right after each one. Stop either with Ctrl+C.
Notice: we never called on_message() or publish_hello() ourselves —
ROS calls them for us, on its own schedule (the timer) or in reaction to
incoming data (the subscription). That handoff of control to the framework
is the main mental shift when learning ROS.
listener.py before talker.py? What about
the other way around? Why?python3 listener.py again, so two
listeners are running at once. What happens? What does this tell you
about how many subscribers a topic can have?talker.py, change self.create_timer(1.0, ...) to 0.2. What
changes when you rerun it?ros2 topic list in a new terminal while talker.py is running. Then
try ros2 topic echo /hello_topic — this is ROS’s own built-in listener,
with no code of yours involved at all. What does that tell you about what
a “topic” actually is?talker.py and listener.py are standalone scripts — real ROS
code usually lives in a proper package (built with colcon), which
matters once you want to share code or launch many nodes together with a
single command via a launch file.We skipped the colcon build / ROS package step entirely and just ran the .py files directly with python3. This works because rclpy is a normal
importable Python library once ROS is sourced — but it means these scripts can’t be launched with ros2 run or combined into launch files the way a proper package’s nodes can.